Skill: stanistolberg/openclaw-memory-max (version 3.0.4)

--- name: openclaw-memory-max description: SOTA Memory Suite — auto-recall, cross-encoder reranking, multi-hop deep search, causal knowledge graph, episodic memory, and nightly sleep-cycle consolidation. metadata: openclaw: emoji: "🧠" homepage: "https://github.com/stanistolberg/openclaw-memory-max" --- # OpenClaw Memory Max You have the **Memory Max** SOTA memory system. It upgrades your memory capabilities far beyond the default memory-core plugin. ## What's Active ### Automatic (no action needed) - **Auto-Recall**: Before every turn, your most relevant memories are automatically injected into your context as `<relevant-memories>` XML blocks. You don't need to search — relevant context appears automatically. - **Auto-Capture**: After conversations, high-value user messages (rules, corrections, preferences) are automatically captured for the nightly consolidation cycle. - **Compaction Rescue**: When the context window is compressed, important content is rescued before it's evicted. - **Episodic Memory**: Each session is logged as an episode with timestamps, tools used, and key decisions. - **Sleep Cycle**: An in-process scheduler runs maintenance every ~24h — prunes the causal graph, decays stale utility scores, truncates old logs, and writes consolidation context for the next session. ### Tools Available #### `precision_memory_search` Cross-encoder reranked search with utility weighting. Returns the top K most relevant memories. ```json {"query": "deployment configuration", "topK": 5} ``` Use this when you need to find specific information in memory. More precise than the default memory search — uses a cross-encoder model that reads query + candidate together, not just cosine similarity. #### `deep_memory_search` Multi-hop retrieval. Searches once, extracts key concepts from results, searches again with those concepts, then merges everything. ```json {"query": "why did the migration fail last time"} ``` Use this for complex questions where the answer might be spread across multiple related memories. #### `reward_memory_utility` Reinforce a memory that proved useful. Increases its future retrieval priority. ```json {"memoryId": "abc-123", "rewardScalar": 0.2} ``` Call this after a memory helped you give a correct answer. #### `penalize_memory_utility` Penalize a memory that caused a hallucination or was irrelevant. ```json {"memoryId": "abc-123", "penaltyScalar": 0.2} ``` Call this when a retrieved memory led you astray. #### `memory_graph_add` Log a cause-action-effect chain. Automatically deduplicates against existing chains. ```json {"cause": "nginx misconfigured", "action": "added proxy_pass", "effect": "site loaded", "outcome": "success", "tags": ["nginx"]} ``` Call this AFTER completing any meaningful action to build your experience database. #### `memory_graph_query` Search past experience using semantic matching. ```json {"query": "website not loading", "outcomeFilter": "success"} ``` Call this BEFORE taking major actions to check what worked or failed in the past. #### `memory_graph_summary` Get a digest of all learned causal knowledge — success/failure counts, most-frequent patterns, recent outcomes. ```json {} ``` Useful at the start of a session to bootstrap your awareness. #### `compress_context` Signal that context compression is needed. Returns what was rescued from the last compaction. ```json {"compression_reason": "context window approaching limit after long debugging session"} ``` ## Rules 1. **Auto-recall is always on** — you will see `<relevant-memories>` blocks in your context. Use them. Don't ignore injected memories. 2. **Reward useful memories** — when a memory helps you answer correctly, call `reward_memory_utility`. This trains the retrieval system. 3. **Penalize bad memories** — when a memory causes a hallucination, call `penalize_memory_utility`. This prevents future mistakes. 4. **Log causal chains** — after significant actions (tool use, decisions, fixes), call `memory_graph_add`. Your future self will thank you. 5. **Check experience before acting** — before major actions, call `memory_graph_query` to see if you've encountered this situation before. 6. **Use deep search for complex questions** — if `precision_memory_search` doesn't find what you need, try `deep_memory_search` which follows concept chains across memories. ## Configuration All features are controlled via `configSchema` in the plugin manifest. Users configure these in their OpenClaw settings: | Option | Default | Description | |---|---|---| | `enableRulePinning` | `false` | YAML rule pinning from MEMORY.md into system prompt. **Off by default** — must be explicitly opted in. | | `enableAutoCapture` | `false` | Automatic capture of high-value user messages to sidecar files. **Off by default** — opt in if you want persistent message logging. | | `enableAutoRecall` | `true` | Automatic memory injection before each agent turn. | ## YAML Rule Pinning (opt-in) **Disabled by default.** Must be enabled via `enableRulePinning: true` in plugin config. When enabled, users can pin critical constraints into the system prompt by adding a YAML block to MEMORY.md: ```markdown <!--yaml rules: - weight: 1.0 constraint: "Never delete production data" - weight: 0.5 preference: "Prefer TypeScript over JavaScript" --> ``` Rules with weight >= 1.0 appear as CRITICAL CONSTRAINTs in your prompt. Always obey them. **Security note**: Only enable this if you control write access to your `~/.openclaw/memory/MEMORY.md` file. Any process that can write to that file could influence agent behavior when pinning is enabled.

path: README.md
content: # openclaw-memory-max `v3.0.3`

> **SOTA Memory Suite for OpenClaw** — Turn your lobster into an agent that actually learns from experience, recalls relevant context automatically, and never forgets what matters.

## Why This Exists

Out of the box, OpenClaw retrieves memories by embedding similarity — a flat cosine score. That works for simple recall, but it breaks down fast:

- **The agent forgets useful context** because retrieval is passive — the agent has to explicitly search. Most turns get zero memory context.
- **The same low-quality memory keeps surfacing** because there's no feedback loop — retrieval never learns which memories actually helped.
- **Critical rules get buried** under hundreds of entries. "NEVER delete production data" has the same weight as a note about your editor theme.
- **The agent repeats mistakes** because it has no structured record of what it tried, what worked, and what failed.
- **Long conversations degrade** as the context window fills with stale turns. Important decisions get compacted away and lost.
- **Every session starts from zero** — no episodic awareness of past sessions, no accumulated wisdom.

Memory-max solves each of these with dedicated modules that work together as a single plugin.

## Architecture Overview

```
                    ┌─────────────────────────────────────┐
  User message ───► │         before_agent_start           │
                    │  Auto-Recall: FTS5 → Cross-Encoder   │
                    │  → Utility Weight → Inject Context    │
                    │  + Causal Graph Experience Lookup     │
                    └──────────────┬──────────────────────┘
                                   │ <relevant-memories> XML block
                                   ▼
                    ┌─────────────────────────────────────┐
                    │          Agent Conversation           │
                    │                                       │
                    │  8 tools available:                   │
                    │  • precision_memory_search (top-K)    │
                    │  • deep_memory_search (multi-hop)     │
                    │  • reward/penalize_memory_utility     │
                    │  • memory_graph_add/query/summary     │
                    │  • compress_context                   │
                    └──────────────┬──────────────────────┘
                                   │
                    ┌──────────────┼──────────────────────┐
                    │              │                       │
              agent_end    before_compaction        session_end
                    │              │                       │
             Auto-Capture    Memory Rescue        Episode Archive
            (rules, prefs)  (save before evict)   (session summary)
                    │              │                       │
                    ▼              ▼                       ▼
              auto_captured.jsonl              episodes.jsonl
                    │
           ┌────────┴─────────┐
           │  Sleep Cycle 3AM  │
           │  • Merge → MEMORY │
           │  • Prune graph    │
           │  • Decay scores   │
           │  • Truncate logs  │
           └──────────────────┘
```

## What It Does

### Lifecycle Hooks (Proactive — No Agent Action Required)

#### Auto-Recall (`before_agent_start`)
Every time the agent starts a turn, memory-max automatically:
1. Takes the user's latest message
2. Runs an FTS5/BM25 pre-filter against the memory database (~0ms)
3. Cross-encoder reranks the top 20 candidates (~200ms, cached model)
4. Multiplies by utility scores (reinforcement-weighted)
5. Queries the causal knowledge graph for relevant past experience
6. Injects the top results as an XML block directly into the agent's context

The agent sees relevant memories **without calling any tool**. This is the single biggest improvement over v2 — the memory system is now proactive, not passive.

#### Auto-Capture (`agent_end`)
After every conversation, memory-max scans the user's messages for high-value content — rules ("always X", "never Y"), corrections ("actually, instead..."), preferences. These are deduplicated against existing memories and stored in `auto_captured.jsonl` for the nightly sleep cycle to consolidate.

#### Compaction Rescue (`before_compaction`)
When OpenClaw compresses the context window, important information can be lost. Memory-max intercepts the compaction event, scans messages about to be evicted, and rescues high-value content before it disappears.

#### Episodic Memory (`session_start` / `session_end`)
Every conversation session is logged as an episode with timestamps, tools used, key decisions, and a summary. This creates temporal awareness — the agent can reason about "what happened last time" and the sleep cycle can detect patterns across sessions.

### Tools (8 Active Tools)

#### 1. Precision Memory Search — `precision_memory_search`
**Problem**: Embedding similarity retrieves "close enough" results, not the best ones.

**Solution**: Two-stage retrieval pipeline:
1. **FTS5/BM25 pre-filter** — narrows to the top 20-30 candidates from the full database (~0ms, pure SQLite)
2. **Cross-encoder rerank** — `ms-marco-MiniLM-L-6-v2` reads query + candidate together as a single sequence, catching semantic relationships cosine similarity misses (negation, implication, multi-hop reasoning)
3. **Utility weighting** — final score = `semantic_score × utility_score`
4. **Returns top-K results** (default 5, configurable up to 10) — not just the single best match

The cross-encoder model loads once and stays cached for the process lifetime (~80 MB resident, eliminates the ~500ms reload that v2 paid on every call). Runs entirely locally via ONNX — no API calls.

#### 2. Deep Multi-Hop Search — `deep_memory_search`
**Problem**: Important information is often connected through intermediate memories that wouldn't match the original query.

**Solution**: Three-stage retrieval:
1. **Hop 1** — Standard precision search for the query (top 5)
2. **Entity extraction** — Pull key terms from hop 1 results that aren't in the original query
3. **Hop 2** — Search again using extracted concepts (top 5)
4. **Merge + deduplicate** — Combine and rank all unique results

This finds information that is connected through intermediate memories — e.g., querying "deployment issues" might surface a memory about "nginx config" through a hop-1 result about "reverse proxy errors".

#### 3. Reward Memory Utility — `reward_memory_utility`
Increment a memory's utility score (+0.1 to +0.3) after it proved useful. Creates a reinforcement loop: memories that help get surfaced more, memories that mislead get buried.

#### 4. Penalize Memory Utility — `penalize_memory_utility`
Decrement a memory's utility score (-0.1 to -0.3) after it caused a hallucination or was irrelevant.

#### 5. Causal Knowledge Graph — `memory_graph_add`
Log a `cause → action → effect` chain tagged with `success`, `failure`, or `unknown`. The graph automatically **deduplicates**: if a semantically similar chain exists (cross-encoder score > 0.85), it merges instead of creating a duplicate — bumping the frequency counter and updating the outcome.

#### 6. Graph Query — `memory_graph_query`
Search the causal graph using **cross-encoder semantic matching** (not just keyword overlap). Supports tag-based pre-filtering for fast lookups. Returns the top 5 most relevant past experience chains.

#### 7. Graph Summary — `memory_graph_summary`
Digest of all learned causal knowledge: success/failure counts, most-frequent patterns, recent wins and failures. Useful for session bootstrapping.

#### 8. Context Compression — `compress_context`
Signal that the context window is overloaded. Returns what was rescued from the last compaction event and recent auto-captures — giving the agent visibility into what the memory system saved.

### Background Systems

#### Semantic YAML Weighter
Monitors `MEMORY.md` every 15s for YAML-fenced rule blocks. Rules with `weight >= 1.0` get pinned directly into the system prompt as `CRITICAL CONSTRAINT`s, bypassing retrieval entirely:

```markdown
<!--yaml
rules:
  - weight: 1.0
    constraint: "Never delete production data"
  - weight: 1.0
    constraint: "Always confirm before sending external messages"
  - weight: 0.5
    preference: "Prefer TypeScript over JavaScript"
-->
```

Only weight >= 1.0 rules get pinned. Everything else stays in normal retrieval.

#### In-Process Sleep Cycle
An in-process scheduler (no `child_process`, no shell exec) that runs maintenance every ~24 hours:
1. **Prune the causal graph** — remove nodes older than 90 days with frequency=1, hard cap at 500 nodes
2. **Decay utility scores** — multiply all scores by 0.99x, preventing stale high-scoring memories from dominating forever
3. **Truncate logs** — remove auto-captures and episodes older than 30 days
4. **Write consolidation context** — summarizes recent auto-captures and episodes to `consolidation_context.md` for the next agent session to reference

The scheduler checks every 6 hours but only runs maintenance once per 20-hour window (dedup guard). No external cron or shell commands needed — runs entirely within the plugin process.

## How It's Built

**Pure JavaScript/TypeScript** — no native binaries, no compilation dependencies. OpenClaw's plugin installer uses `npm install --ignore-scripts`, so native modules would fail silently.

| Component | Implementation | Why |
|---|---|---|
| Cross-encoder | `@huggingface/transformers` (ONNX) | Pure JS, runs locally, no API keys, singleton cached |
| SQLite access | `sql.js` (Emscripten/WASM) | Read-only against OpenClaw's DB, no native build step |
| FTS5 pre-filter | sql.js + JS fallback | Fast BM25 ranking, falls back to keyword matching if FTS5 unavailable |
| Utility scores | JSON sidecar file | Plugin-owned, no concurrent-write risk with OpenClaw |
| Knowledge graph | JSON file | Zero dependencies, human-readable, auto-pruned |
| Episodic memory | JSONL append-only | Low-overhead, auto-truncated |
| Auto-captures | JSONL append-only | Deduped against DB, auto-truncated |
| YAML parsing | `yaml` | Standard YAML 1.2 parser |

**Architecture principle**: This plugin is **read-only** against OpenClaw's `main.sqlite`. It never writes to, modifies, or locks the core memory database. All plugin state lives in sidecar files that only this plugin touches:

| File | Purpose | Max Size |
|---|---|---|
| `utility_scores.json` | Reinforcement scores per memory ID | ~100 KB |
| `causal_graph.json` | Cause→action→effect chains (500 node cap) | ~500 KB |
| `auto_captured.jsonl` | Auto-captured high-value messages (30-day cap) | ~2 MB |
| `episodes.jsonl` | Session episode summaries (30-day cap) | ~1 MB |

Total disk overhead: **< 4 MB**. RAM overhead: **~92 MB** (mostly the cross-encoder model singleton).

## Requirements

- OpenClaw `v2026.2.x` or later
- Node.js 18+
- ~100 MB RAM for the cross-encoder model
- ~800 MB disk for `node_modules` (first install)

## Installation

```bash
cd ~/.openclaw/extensions
git clone https://github.com/stanistolberg/openclaw-memory-max
cd openclaw-memory-max
npm install
npm run build
openclaw plugins enable openclaw-memory-max
# Restart the gateway to load the plugin
```

The plugin declares `kind: "memory"` which means it takes the exclusive memory slot. Enabling it automatically disables `memory-core` and `memory-lancedb`.

## Verify Installation

```bash
# Check plugin status
openclaw plugins list | grep memory-max

# Check sidecar files
npm run audit

# Check gateway logs for initialization
journalctl --user -u openclaw-gateway --since "5 min ago" | grep memory-max
```

You should see:
```
[openclaw-memory-max] Initializing SOTA Memory Cluster v3...
[openclaw-memory-max] ✓ Precision Reranker + Deep Multi-Hop Search active.
[openclaw-memory-max] ✓ Semantic Rule Weighter watching MEMORY.md.
[openclaw-memory-max] ✓ Context Compressor registered.
[openclaw-memory-max] ✓ Causal Knowledge Graph live (semantic + dedup).
[openclaw-memory-max] ✓ Lifecycle hooks registered (auto-recall, auto-capture, compaction-rescue).
[openclaw-memory-max] ✓ Episodic Memory hooks registered (session segmentation).
[openclaw-memory-max] All systems nominal. SOTA Memory Matrix ACTIVE.
```

## Project Structure

```
src/
  index.ts        — Plugin entrypoint, registers all modules + hooks
  reranker.ts     — Cross-encoder singleton, precision search, deep search, reward/penalize (4 tools)
  hooks.ts        — Lifecycle hooks: auto-recall, auto-capture, compaction rescue
  episodic.ts     — Session segmentation via session_start/session_end hooks
  graph.ts        — Causal knowledge graph with semantic search, dedup, pruning (3 tools)
  weighter.ts     — Semantic YAML rule pinner (watches MEMORY.md every 15s)
  compressor.ts   — Context compression tool with rescue data (1 tool)
  db.ts           — Read-only SQLite access, FTS5 pre-filter, utility score sidecar
  sleep-cycle.ts  — Nightly consolidation cron with decay, pruning, truncation
dist/             — Compiled CommonJS output (ships with repo)
```

## Research Basis

| Feature | Inspired By |
|---|---|
| Cross-encoder reranking | ColBERT, MS MARCO reranking benchmarks |
| Utility-weighted retrieval | MemRL (reinforcement-weighted memory), AgeMem (utility decay) |
| Multi-hop search | STORM (iterative retrieval), RAG-Fusion (reciprocal rank fusion) |
| Causal knowledge graph | ActMem (action-conditioned memory), CausalKG |
| Episodic memory | MemGPT/Letta (episodic vs semantic separation) |
| Auto-recall hooks | Zep, Mem0 (proactive memory injection) |
| Sleep cycle consolidation | Memory consolidation theory (Complementary Learning Systems) |
| Utility decay | Ebbinghaus forgetting curve applied to retrieval scores |

## Changelog

### 3.0.0

**Proactive memory system — the agent no longer needs to explicitly search.**

- **NEW: Lifecycle hooks** — `before_agent_start` (auto-recall), `agent_end` (auto-capture), `before_compaction` (memory rescue)
- **NEW: Episodic memory** — `session_start`/`session_end` hooks track sessions as episodes with summaries, tools used, and key decisions
- **NEW: `deep_memory_search` tool** — multi-hop retrieval: query → extract concepts → re-query → merge + deduplicate
- **NEW: FTS5/BM25 pre-filter** — narrows candidates before cross-encoder scoring (20 instead of 100), with JS keyword fallback
- **NEW: Cross-encoder model singleton** — loads once, stays cached (~500ms saved per search call)
- **NEW: Graph deduplication** — similar chains auto-merge (cross-encoder score > 0.85) with frequency tracking
- **NEW: Graph pruning** — removes old low-frequency nodes, hard cap at 500
- **NEW: Tag-based graph index** — fast pre-filtering when query matches known tags
- **NEW: Utility score decay** — scores decay by 0.99x nightly for inactive memories
- **NEW: Auto-truncation** — captures and episodes older than 30 days are cleaned up
- `precision_memory_search` now returns top-K results (was single result)
- Graph similarity upgraded from Jaccard token overlap to cross-encoder semantic matching
- `compress_context` now reports rescued content from last compaction event
- Sleep cycle enhanced: reads auto-captures + episodes, runs graph pruning + score decay
- `memory_graph_summary` now includes most-frequent patterns

### 2.0.0
- Migrated to current OpenClaw plugin API (`name`/`parameters`/`execute` with structured returns)
- Replaced native deps (`better-sqlite3`, `@xenova/transformers`) with pure-JS alternatives (`sql.js`, `@huggingface/transformers`)
- Utility scores moved from `main.sqlite` column to plugin-owned sidecar (`utility_scores.json`)
- Plugin is now read-only against OpenClaw's `main.sqlite`
- Added `configSchema` to manifest (required by current OpenClaw loader)
- Fixed template literal parse errors, YAML fence regex, destructive systemPrompt overwrite

### 1.1.0
- Initial release

## License

MIT


path: dist/compressor.d.ts
content: /** Called by the before_compaction hook in hooks.ts to report rescued items. */
export declare function reportCompactionRescue(count: number, items: string[]): void;
export declare function registerCompressor(api: any): void;


path: dist/compressor.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reportCompactionRescue = reportCompactionRescue;
exports.registerCompressor = registerCompressor;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
function getBaseDir() {
    return process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
}
function getCapturedPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'auto_captured.jsonl');
}
function jsonResult(payload) {
    return {
        content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
        details: payload
    };
}
// Track what the before_compaction hook rescued
let lastCompactionRescue = { count: 0, timestamp: 0, items: [] };
/** Called by the before_compaction hook in hooks.ts to report rescued items. */
function reportCompactionRescue(count, items) {
    lastCompactionRescue = { count, timestamp: Date.now(), items: items.slice(0, 5) };
}
function registerCompressor(api) {
    api.registerTool({
        name: 'compress_context',
        description: 'Signal that context compression is needed and review what was rescued from the last compaction. Call when the context window feels overloaded. Returns rescued memories from recent compaction events plus an advisory to the runtime.',
        parameters: {
            type: 'object',
            properties: {
                compression_reason: { type: 'string', description: 'Why you decided to compress the context now.' }
            },
            required: ['compression_reason']
        },
        async execute(_toolCallId, args) {
            // Read recent auto-captured entries (last 24h) for context
            const recentCaptures = [];
            try {
                const capturePath = getCapturedPath();
                if (fs_1.default.existsSync(capturePath)) {
                    const cutoff = Date.now() - (24 * 60 * 60 * 1000);
                    const lines = fs_1.default.readFileSync(capturePath, 'utf8').split('\n').filter(Boolean);
                    for (const line of lines) {
                        try {
                            const entry = JSON.parse(line);
                            if (entry.timestamp >= cutoff) {
                                recentCaptures.push(`[${entry.source}] ${entry.text.substring(0, 100)}`);
                            }
                        }
                        catch { /* skip */ }
                    }
                }
            }
            catch { /* non-critical */ }
            return jsonResult({
                status: 'compression_advisory',
                reason: args.compression_reason,
                message: 'Context compression requested. The runtime handles actual eviction. High-value content is auto-rescued by the before_compaction hook.',
                lastRescue: lastCompactionRescue.count > 0 ? {
                    rescued: lastCompactionRescue.count,
                    when: new Date(lastCompactionRescue.timestamp).toISOString(),
                    preview: lastCompactionRescue.items
                } : null,
                recentAutoCaptures: recentCaptures.length,
                capturePreview: recentCaptures.slice(0, 3)
            });
        }
    });
}


path: dist/db.d.ts
content: /** Get utility score for a memory ID (default 0.5). */
export declare function getUtilityScore(id: string): number;
/** Fast BM25 pre-filter via FTS5. Falls back to keyword matching if FTS5 unavailable. */
export declare function searchChunksFTS(query: string, limit?: number): Promise<any[]>;
/** Check for duplicate content. Returns chunks with high text overlap. */
export declare function findSimilarChunks(text: string, limit?: number): Promise<any[]>;
/** One-time schema check — read-only, no writes to main.sqlite. */
export declare function ensureUtilityColumn(): Promise<void>;
/** Update utility score in the sidecar file. Validates the ID exists in main.sqlite first. */
export declare function rewardMemory(id: string, scalar?: number): Promise<boolean>;
/** Query chunks table — read-only from main.sqlite, enriched with sidecar scores. */
export declare function queryChunks(limit?: number): Promise<any[]>;


path: dist/db.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUtilityScore = getUtilityScore;
exports.searchChunksFTS = searchChunksFTS;
exports.findSimilarChunks = findSimilarChunks;
exports.ensureUtilityColumn = ensureUtilityColumn;
exports.rewardMemory = rewardMemory;
exports.queryChunks = queryChunks;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const sql_js_1 = __importDefault(require("sql.js"));
let _SQL = null;
function getBaseDir() {
    return process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
}
function getDbPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'main.sqlite');
}
function getScoresPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'utility_scores.json');
}
async function getSqlJs() {
    if (!_SQL)
        _SQL = await (0, sql_js_1.default)();
    return _SQL;
}
// ── Read-only access to OpenClaw's main.sqlite ──────────────────────────
/** Open a read-only snapshot. Caller must close(). */
async function openDb() {
    const SQL = await getSqlJs();
    const dbPath = getDbPath();
    if (!fs_1.default.existsSync(dbPath))
        return null;
    const buffer = fs_1.default.readFileSync(dbPath);
    return new SQL.Database(buffer);
}
function queryAll(db, sql, params) {
    const stmt = db.prepare(sql);
    if (params)
        stmt.bind(params);
    const rows = [];
    while (stmt.step()) {
        rows.push(stmt.getAsObject());
    }
    stmt.free();
    return rows;
}
function loadScores() {
    const p = getScoresPath();
    if (!fs_1.default.existsSync(p))
        return {};
    try {
        return JSON.parse(fs_1.default.readFileSync(p, 'utf8'));
    }
    catch {
        return {};
    }
}
function saveScores(scores) {
    const p = getScoresPath();
    const dir = path_1.default.dirname(p);
    if (!fs_1.default.existsSync(dir))
        fs_1.default.mkdirSync(dir, { recursive: true });
    fs_1.default.writeFileSync(p, JSON.stringify(scores, null, 2));
}
/** Get utility score for a memory ID (default 0.5). */
function getUtilityScore(id) {
    const scores = loadScores();
    return scores[id] !== undefined ? scores[id] : 0.5;
}
// ── FTS5 Pre-filter + Dedup ─────────────────────────────────────────────
/** Fast BM25 pre-filter via FTS5. Falls back to keyword matching if FTS5 unavailable. */
async function searchChunksFTS(query, limit = 20) {
    const db = await openDb();
    if (!db)
        return [];
    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table' OR type='virtual'");
        const hasFTS = tables.some((t) => t.name === 'chunks_fts');
        let rows;
        if (hasFTS) {
            // FTS5 MATCH — sanitize query for FTS syntax (wrap tokens in quotes)
            const sanitized = query.replace(/[^\w\s]/g, '').split(/\s+/).filter(Boolean).map(t => `"${t}"`).join(' OR ');
            if (!sanitized) {
                db.close();
                return [];
            }
            try {
                rows = queryAll(db, `SELECT chunks_fts.id, chunks_fts.text, chunks_fts.path, chunks_fts.source, rank
                    FROM chunks_fts
                    WHERE chunks_fts MATCH ?
                    ORDER BY rank
                    LIMIT ?`, [sanitized, limit]);
            }
            catch {
                // FTS5 might not be compiled into sql.js — fall back
                rows = [];
            }
        }
        else {
            rows = [];
        }
        // Fallback: keyword pre-filter in JS if FTS5 unavailable or returned nothing
        if (rows.length === 0) {
            const allChunks = queryAll(db, `SELECT * FROM chunks ORDER BY rowid DESC LIMIT 200`);
            const queryTokens = new Set(query.toLowerCase().split(/\W+/).filter(t => t.length > 2));
            rows = allChunks.filter((row) => {
                const text = (row.text || '').toLowerCase();
                let matches = 0;
                for (const token of queryTokens) {
                    if (text.includes(token))
                        matches++;
                }
                return matches >= Math.min(2, queryTokens.size);
            }).slice(0, limit);
        }
        db.close();
        // Enrich with utility scores
        const scores = loadScores();
        for (const row of rows) {
            const id = row.id || row.rowid || '';
            row.utility_score = scores[id] !== undefined ? scores[id] : 0.5;
        }
        return rows;
    }
    catch (e) {
        db.close();
        console.error('[openclaw-memory-max][db] FTS search failed:', e.message);
        return [];
    }
}
/** Check for duplicate content. Returns chunks with high text overlap. */
async function findSimilarChunks(text, limit = 5) {
    // Use FTS5 with the first ~50 words of the text as query
    const queryText = text.split(/\s+/).slice(0, 50).join(' ');
    return searchChunksFTS(queryText, limit);
}
// ── Public API ──────────────────────────────────────────────────────────
/** One-time schema check — read-only, no writes to main.sqlite. */
async function ensureUtilityColumn() {
    const db = await openDb();
    if (!db) {
        console.log('[openclaw-memory-max][db] DB file not found. Awaiting context instantiation.');
        return;
    }
    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
        console.log('[openclaw-memory-max][db] Auditing tables:', tables.map((t) => t.name).join(', '));
        const hasChunks = tables.some((t) => t.name === 'chunks');
        if (hasChunks) {
            console.log('[openclaw-memory-max][db] Memory table found. Utility scores stored in sidecar file.');
        }
        else {
            console.log('[openclaw-memory-max][db] Memory table not found. Awaiting context instantiation.');
        }
        db.close();
    }
    catch (e) {
        db.close();
        console.error('[openclaw-memory-max][db] SQLite Audit Failed:', e.message);
    }
}
/** Update utility score in the sidecar file. Validates the ID exists in main.sqlite first. */
async function rewardMemory(id, scalar = 0.1) {
    try {
        // Verify the memory ID actually exists in main.sqlite
        const db = await openDb();
        if (!db)
            return false;
        let found = false;
        try {
            const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
            const targets = ['chunks', 'memories', 'documents', 'episodic_memory'];
            for (const tbl of targets) {
                if (!tables.some((t) => t.name === tbl))
                    continue;
                const rows = queryAll(db, `SELECT 1 FROM ${tbl} WHERE id = ? LIMIT 1`, [id]);
                if (rows.length > 0) {
                    found = true;
                    break;
                }
            }
        }
        finally {
            db.close();
        }
        if (!found)
            return false;
        const scores = loadScores();
        const current = scores[id] !== undefined ? scores[id] : 0.5;
        const updated = Math.max(0.0, Math.min(1.0, current + scalar));
        scores[id] = updated;
        saveScores(scores);
        console.log(`[openclaw-memory-max][db] Updated utility for ${id}: ${current.toFixed(2)} → ${updated.toFixed(2)}`);
        return true;
    }
    catch (e) {
        console.error('[openclaw-memory-max][db] Failed to update utility score:', e.message);
        return false;
    }
}
/** Query chunks table — read-only from main.sqlite, enriched with sidecar scores. */
async function queryChunks(limit = 100) {
    const db = await openDb();
    if (!db)
        return [];
    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
        const hasChunks = tables.some((t) => t.name === 'chunks');
        if (!hasChunks) {
            db.close();
            return [];
        }
        const rows = queryAll(db, `SELECT * FROM chunks ORDER BY rowid DESC LIMIT ?`, [limit]);
        db.close();
        // Enrich with sidecar utility scores
        const scores = loadScores();
        for (const row of rows) {
            const id = row.id || row.rowid || '';
            row.utility_score = scores[id] !== undefined ? scores[id] : 0.5;
        }
        return rows;
    }
    catch (e) {
        db.close();
        console.error('[openclaw-memory-max][db] Failed to query chunks:', e.message);
        return [];
    }
}


path: dist/episodic.d.ts
content: interface Episode {
    id: string;
    sessionId: string;
    start: number;
    end?: number;
    summary?: string;
    keyDecisions: string[];
    toolsUsed: string[];
    outcome?: string;
}
/** Read recent episodes (last N days). */
export declare function readRecentEpisodes(days?: number): Episode[];
/** Truncate episodes older than N days. */
export declare function truncateEpisodes(days?: number): number;
export declare function registerEpisodic(api: any): void;
export {};


path: dist/episodic.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readRecentEpisodes = readRecentEpisodes;
exports.truncateEpisodes = truncateEpisodes;
exports.registerEpisodic = registerEpisodic;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const TAG = '[openclaw-memory-max][episodic]';
function getBaseDir() {
    return process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
}
function getEpisodesPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'episodes.jsonl');
}
function appendEpisode(episode) {
    const p = getEpisodesPath();
    const dir = path_1.default.dirname(p);
    if (!fs_1.default.existsSync(dir))
        fs_1.default.mkdirSync(dir, { recursive: true });
    fs_1.default.appendFileSync(p, JSON.stringify(episode) + '\n');
}
/** Read recent episodes (last N days). */
function readRecentEpisodes(days = 7) {
    const p = getEpisodesPath();
    if (!fs_1.default.existsSync(p))
        return [];
    const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
    const lines = fs_1.default.readFileSync(p, 'utf8').split('\n').filter(Boolean);
    const episodes = [];
    for (const line of lines) {
        try {
            const ep = JSON.parse(line);
            if (ep.start >= cutoff)
                episodes.push(ep);
        }
        catch { /* skip malformed lines */ }
    }
    return episodes;
}
/** Truncate episodes older than N days. */
function truncateEpisodes(days = 30) {
    const p = getEpisodesPath();
    if (!fs_1.default.existsSync(p))
        return 0;
    const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
    const lines = fs_1.default.readFileSync(p, 'utf8').split('\n').filter(Boolean);
    const kept = [];
    let removed = 0;
    for (const line of lines) {
        try {
            const ep = JSON.parse(line);
            if (ep.start >= cutoff) {
                kept.push(line);
            }
            else {
                removed++;
            }
        }
        catch {
            // Drop malformed
            removed++;
        }
    }
    fs_1.default.writeFileSync(p, kept.join('\n') + (kept.length > 0 ? '\n' : ''));
    return removed;
}
// ── Extract session metadata from hook context ──────────────────────────
function extractToolsUsed(context) {
    const tools = new Set();
    if (Array.isArray(context.messages)) {
        for (const msg of context.messages) {
            if (msg.role === 'assistant' && Array.isArray(msg.content)) {
                for (const part of msg.content) {
                    if (part.type === 'tool_use' && part.name) {
                        tools.add(part.name);
                    }
                }
            }
            // Also check tool_calls format
            if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
                for (const tc of msg.tool_calls) {
                    if (tc.function?.name)
                        tools.add(tc.function.name);
                }
            }
        }
    }
    return [...tools];
}
function extractDecisions(context) {
    const decisions = [];
    if (!Array.isArray(context.messages))
        return decisions;
    const decisionPatterns = [
        /\bdecided to\b/i,
        /\bchose to\b/i,
        /\bwill use\b/i,
        /\bswitching to\b/i,
        /\binstead of\b/i,
    ];
    for (const msg of context.messages) {
        if (msg.role === 'assistant' && typeof msg.content === 'string') {
            for (const pattern of decisionPatterns) {
                if (pattern.test(msg.content)) {
                    // Extract the sentence containing the decision
                    const sentences = msg.content.split(/[.!?]+/).filter(Boolean);
                    for (const s of sentences) {
                        if (pattern.test(s) && s.trim().length > 15 && s.trim().length < 200) {
                            decisions.push(s.trim());
                        }
                    }
                }
            }
        }
    }
    return decisions.slice(0, 5); // Cap at 5 decisions per session
}
// ── Hook Registration ───────────────────────────────────────────────────
const activeSessions = new Map();
function registerEpisodic(api) {
    // session_start: create episode
    api.on('session_start', async (context) => {
        try {
            const sessionId = context.sessionId || context.id || `session-${Date.now()}`;
            const episode = {
                id: `ep-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`,
                sessionId,
                start: Date.now(),
                keyDecisions: [],
                toolsUsed: []
            };
            activeSessions.set(sessionId, episode);
            console.log(`${TAG} Episode started: ${episode.id}`);
        }
        catch (e) {
            console.error(`${TAG} session_start failed:`, e.message);
        }
    });
    // session_end: finalize and store episode
    api.on('session_end', async (context) => {
        try {
            const sessionId = context.sessionId || context.id || '';
            let episode = activeSessions.get(sessionId);
            if (!episode) {
                // Create a retroactive episode
                episode = {
                    id: `ep-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`,
                    sessionId,
                    start: Date.now() - 60000, // Approximate
                    keyDecisions: [],
                    toolsUsed: []
                };
            }
            episode.end = Date.now();
            episode.toolsUsed = extractToolsUsed(context);
            episode.keyDecisions = extractDecisions(context);
            // Generate a brief summary from the last user message
            const messages = Array.isArray(context.messages) ? context.messages : [];
            const lastUser = [...messages].reverse().find((m) => m.role === 'user');
            if (lastUser) {
                const text = typeof lastUser.content === 'string' ? lastUser.content : '';
                episode.summary = text.length > 200 ? text.substring(0, 200) + '...' : text;
            }
            appendEpisode(episode);
            activeSessions.delete(sessionId);
            console.log(`${TAG} Episode ended: ${episode.id} (${episode.toolsUsed.length} tools, ${episode.keyDecisions.length} decisions)`);
        }
        catch (e) {
            console.error(`${TAG} session_end failed:`, e.message);
        }
    });
    console.log('[openclaw-memory-max] ✓ Episodic Memory hooks registered (session segmentation).');
}


path: dist/graph.d.ts
content: /** Prune low-value old nodes. Called by sleep cycle. */
export declare function pruneGraph(): {
    removed: number;
    remaining: number;
};
/** Hook-friendly graph query (no tool wrapper). Used by hooks.ts for auto-recall. */
export declare function queryGraphForHook(query: string, topK?: number): Promise<Array<{
    cause: string;
    action: string;
    effect: string;
    outcome: string;
}>>;
export declare function registerCausalGraph(api: any): void;


path: dist/graph.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.pruneGraph = pruneGraph;
exports.queryGraphForHook = queryGraphForHook;
exports.registerCausalGraph = registerCausalGraph;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const crypto_1 = __importDefault(require("crypto"));
const reranker_1 = require("./reranker");
const MAX_GRAPH_NODES = 500;
function getGraphPath() {
    const baseDir = process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
    return path_1.default.join(baseDir, 'memory', 'causal_graph.json');
}
function loadGraph() {
    const gPath = getGraphPath();
    if (!fs_1.default.existsSync(gPath)) {
        return { version: '2.0.0', nodes: [] };
    }
    try {
        const graph = JSON.parse(fs_1.default.readFileSync(gPath, 'utf8'));
        // Migrate v1 nodes: add missing fields
        for (const node of graph.nodes) {
            if (node.frequency === undefined)
                node.frequency = 1;
            if (node.lastAccessed === undefined)
                node.lastAccessed = node.timestamp;
        }
        return graph;
    }
    catch {
        return { version: '2.0.0', nodes: [] };
    }
}
function saveGraph(graph) {
    const gPath = getGraphPath();
    const dir = path_1.default.dirname(gPath);
    if (!fs_1.default.existsSync(dir))
        fs_1.default.mkdirSync(dir, { recursive: true });
    fs_1.default.writeFileSync(gPath, JSON.stringify(graph, null, 2));
}
/** Quick Jaccard pre-filter for large graphs before cross-encoder scoring. */
function textSimilarityQuick(a, b) {
    const tokensA = new Set(a.toLowerCase().split(/\W+/).filter(Boolean));
    const tokensB = new Set(b.toLowerCase().split(/\W+/).filter(Boolean));
    const intersection = [...tokensA].filter(t => tokensB.has(t)).length;
    const union = new Set([...tokensA, ...tokensB]).size;
    return union === 0 ? 0 : intersection / union;
}
/** Build tag index for fast tag-based pre-filtering. */
function buildTagIndex(nodes) {
    const index = new Map();
    for (const node of nodes) {
        for (const tag of node.tags) {
            const key = tag.toLowerCase();
            if (!index.has(key))
                index.set(key, []);
            index.get(key).push(node.id);
        }
    }
    return index;
}
/** Semantic scoring: use cross-encoder when available, fall back to Jaccard. */
async function scoreNodes(query, nodes, topK = 5) {
    if (nodes.length === 0)
        return [];
    // Pre-filter: if >30 nodes, narrow to top 20 by Jaccard first
    let candidates = nodes;
    if (candidates.length > 30) {
        const quick = candidates.map(node => ({
            score: textSimilarityQuick(query, `${node.cause} ${node.action} ${node.effect}`),
            node
        }));
        quick.sort((a, b) => b.score - a.score);
        candidates = quick.slice(0, 20).map(q => q.node);
    }
    // Cross-encoder scoring
    try {
        const classifier = await (0, reranker_1.getClassifier)();
        const scored = [];
        for (const node of candidates) {
            const nodeText = `${node.cause} ${node.action} ${node.effect}`;
            const output = await classifier(query, nodeText);
            const score = output[0]?.score || 0;
            scored.push({ score, node });
        }
        scored.sort((a, b) => b.score - a.score);
        return scored.slice(0, topK);
    }
    catch {
        // Fallback to Jaccard if cross-encoder unavailable
        const scored = candidates.map(node => ({
            score: textSimilarityQuick(query, `${node.cause} ${node.action} ${node.effect}`),
            node
        }));
        scored.sort((a, b) => b.score - a.score);
        return scored.slice(0, topK);
    }
}
/** Find a semantically similar node for dedup (cross-encoder score > threshold). */
async function findDuplicate(graph, cause, action, effect, threshold = 0.85) {
    if (graph.nodes.length === 0)
        return null;
    const query = `${cause} ${action} ${effect}`;
    const results = await scoreNodes(query, graph.nodes, 1);
    if (results.length > 0 && results[0].score > threshold) {
        return results[0].node;
    }
    return null;
}
/** Prune low-value old nodes. Called by sleep cycle. */
function pruneGraph() {
    const graph = loadGraph();
    const now = Date.now();
    const NINETY_DAYS = 90 * 24 * 60 * 60 * 1000;
    // Remove: old + low-frequency + not recently accessed
    const before = graph.nodes.length;
    graph.nodes = graph.nodes.filter(node => {
        const age = now - node.timestamp;
        if (age > NINETY_DAYS && node.frequency <= 1 && (now - node.lastAccessed) > NINETY_DAYS) {
            return false; // Prune
        }
        return true;
    });
    // Hard cap: keep top nodes by (frequency * recency)
    if (graph.nodes.length > MAX_GRAPH_NODES) {
        graph.nodes.sort((a, b) => {
            const scoreA = a.frequency * (1 / (1 + (now - a.lastAccessed) / (24 * 60 * 60 * 1000)));
            const scoreB = b.frequency * (1 / (1 + (now - b.lastAccessed) / (24 * 60 * 60 * 1000)));
            return scoreB - scoreA;
        });
        graph.nodes = graph.nodes.slice(0, MAX_GRAPH_NODES);
    }
    saveGraph(graph);
    return { removed: before - graph.nodes.length, remaining: graph.nodes.length };
}
/** Hook-friendly graph query (no tool wrapper). Used by hooks.ts for auto-recall. */
async function queryGraphForHook(query, topK = 2) {
    const graph = loadGraph();
    if (graph.nodes.length === 0)
        return [];
    const results = await scoreNodes(query, graph.nodes, topK);
    // Update lastAccessed on matched nodes
    for (const r of results) {
        r.node.lastAccessed = Date.now();
    }
    if (results.length > 0)
        saveGraph(graph);
    return results
        .filter(r => r.score > 0.2)
        .map(r => ({
        cause: r.node.cause,
        action: r.node.action,
        effect: r.node.effect,
        outcome: r.node.outcome
    }));
}
function jsonResult(payload) {
    return {
        content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
        details: payload
    };
}
function registerCausalGraph(api) {
    // Tool 1: Log a causal chain (with dedup)
    api.registerTool({
        name: 'memory_graph_add',
        description: 'Log a causal chain to long-term memory: what caused you to take an action and what the effect was. Call this AFTER you complete any meaningful action (tool use, decision, fix). Automatically deduplicates against existing chains.',
        parameters: {
            type: 'object',
            properties: {
                cause: { type: 'string', description: 'What situation or trigger prompted the action.' },
                action: { type: 'string', description: 'What you did (tool used, decision made).' },
                effect: { type: 'string', description: 'What happened as a result.' },
                outcome: { type: 'string', enum: ['success', 'failure', 'unknown'], description: 'Did it work?' },
                tags: { type: 'array', items: { type: 'string' }, description: 'Optional topic tags (e.g. ["ssh", "permissions"])' }
            },
            required: ['cause', 'action', 'effect', 'outcome']
        },
        async execute(_toolCallId, args) {
            const graph = loadGraph();
            // Dedup: check if a similar chain already exists
            const existing = await findDuplicate(graph, args.cause, args.action, args.effect);
            if (existing) {
                // Merge: update outcome if different, bump frequency
                existing.frequency++;
                existing.lastAccessed = Date.now();
                if (args.outcome !== existing.outcome && args.outcome !== 'unknown') {
                    existing.outcome = args.outcome;
                }
                // Merge tags
                const existingTags = new Set(existing.tags);
                for (const tag of (args.tags || [])) {
                    existingTags.add(tag);
                }
                existing.tags = [...existingTags];
                saveGraph(graph);
                return jsonResult({ status: 'merged', id: existing.id, frequency: existing.frequency, total: graph.nodes.length });
            }
            const node = {
                id: crypto_1.default.randomUUID(),
                cause: args.cause,
                action: args.action,
                effect: args.effect,
                outcome: args.outcome || 'unknown',
                timestamp: Date.now(),
                tags: args.tags || [],
                frequency: 1,
                lastAccessed: Date.now()
            };
            graph.nodes.push(node);
            saveGraph(graph);
            return jsonResult({ status: 'stored', id: node.id, total: graph.nodes.length });
        }
    });
    // Tool 2: Query for similar causal chains (semantic search)
    api.registerTool({
        name: 'memory_graph_query',
        description: 'Query the causal memory graph for past chains relevant to the current situation. Uses cross-encoder semantic matching, not just keyword overlap. Call this BEFORE taking any major action to check if you have relevant learned experience.',
        parameters: {
            type: 'object',
            properties: {
                query: { type: 'string', description: 'Describe the current situation or intended action.' },
                outcomeFilter: { type: 'string', enum: ['success', 'failure', 'unknown', 'all'], description: 'Filter by outcome type. Default: all.' }
            },
            required: ['query']
        },
        async execute(_toolCallId, args) {
            const graph = loadGraph();
            if (graph.nodes.length === 0) {
                return jsonResult({ status: 'empty', message: 'No causal memory nodes yet.' });
            }
            const outcomeFilter = args.outcomeFilter || 'all';
            let candidates = graph.nodes;
            if (outcomeFilter !== 'all') {
                candidates = candidates.filter(n => n.outcome === outcomeFilter);
            }
            // Tag-based pre-filter: if query matches known tags, prioritize those
            const tagIndex = buildTagIndex(candidates);
            const queryWords = args.query.toLowerCase().split(/\W+/).filter(Boolean);
            const tagMatchIds = new Set();
            for (const word of queryWords) {
                const ids = tagIndex.get(word);
                if (ids)
                    ids.forEach(id => tagMatchIds.add(id));
            }
            // If tag matches found, put them first (but still include others)
            if (tagMatchIds.size > 0 && tagMatchIds.size < candidates.length) {
                const tagged = candidates.filter(n => tagMatchIds.has(n.id));
                const untagged = candidates.filter(n => !tagMatchIds.has(n.id));
                candidates = [...tagged, ...untagged];
            }
            const scored = await scoreNodes(args.query, candidates, 5);
            // Update lastAccessed
            for (const r of scored) {
                r.node.lastAccessed = Date.now();
            }
            if (scored.length > 0)
                saveGraph(graph);
            return jsonResult({
                status: 'results',
                total_in_db: graph.nodes.length,
                results: scored.map(({ score, node }) => ({
                    score: parseFloat(score.toFixed(4)),
                    outcome: node.outcome,
                    cause: node.cause,
                    action: node.action,
                    effect: node.effect,
                    tags: node.tags,
                    frequency: node.frequency,
                    timestamp: new Date(node.timestamp).toISOString()
                }))
            });
        }
    });
    // Tool 3: Get a full summary of everything the agent has learned
    api.registerTool({
        name: 'memory_graph_summary',
        description: 'Returns a digest of all learned causal chains, grouped by outcome. Useful for self-auditing or bootstrapping context at the start of a session.',
        parameters: { type: 'object', properties: {} },
        async execute(_toolCallId, _args) {
            const graph = loadGraph();
            const total = graph.nodes.length;
            const successes = graph.nodes.filter(n => n.outcome === 'success').length;
            const failures = graph.nodes.filter(n => n.outcome === 'failure').length;
            const unknown = graph.nodes.filter(n => n.outcome === 'unknown').length;
            const topFrequency = [...graph.nodes].sort((a, b) => b.frequency - a.frequency).slice(0, 3);
            const recentSuccesses = graph.nodes
                .filter(n => n.outcome === 'success')
                .slice(-3)
                .map(n => `[${n.tags.join(', ') || 'general'}] ${n.action} → ${n.effect}`);
            const recentFailures = graph.nodes
                .filter(n => n.outcome === 'failure')
                .slice(-3)
                .map(n => `[${n.tags.join(', ') || 'general'}] ${n.action} → ${n.effect}`);
            return jsonResult({
                status: 'ok',
                total,
                successes,
                failures,
                unknown,
                most_frequent: topFrequency.map(n => ({
                    action: n.action,
                    frequency: n.frequency,
                    outcome: n.outcome
                })),
                recent_successes: recentSuccesses,
                recent_failures: recentFailures
            });
        }
    });
    console.log('[openclaw-memory-max] Causal Knowledge Graph (3 tools) registered.');
}


path: dist/hooks.d.ts
content: export interface HooksConfig {
    enableAutoCapture?: boolean;
    enableAutoRecall?: boolean;
    enableRulePinning?: boolean;
}
export declare function registerHooks(api: any, config?: HooksConfig): void;


path: dist/hooks.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.registerHooks = registerHooks;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const db_1 = require("./db");
const reranker_1 = require("./reranker");
const graph_1 = require("./graph");
const weighter_1 = require("./weighter");
const TAG = '[openclaw-memory-max][hooks]';
function getBaseDir() {
    return process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
}
function getCapturedPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'auto_captured.jsonl');
}
// ── Importance heuristics ───────────────────────────────────────────────
const IMPORTANCE_PATTERNS = [
    /\balways\b/i,
    /\bnever\b/i,
    /\bremember\b/i,
    /\bfrom now on\b/i,
    /\bimportant\b/i,
    /\bdon'?t forget\b/i,
    /\bmake sure\b/i,
    /\brule\b/i,
    /\bpreference\b/i,
    /\bcorrection\b/i,
    /\bactually\b/i, // often signals a correction
    /\binstead\b/i, // often signals a correction
    /\bnot [\w]+ but\b/i,
];
function isHighValue(text) {
    if (!text || text.length < 20)
        return false;
    let hits = 0;
    for (const pattern of IMPORTANCE_PATTERNS) {
        if (pattern.test(text))
            hits++;
    }
    return hits >= 1;
}
function truncateTokens(text, maxTokens) {
    // Rough approximation: 1 token ≈ 4 chars
    const maxChars = maxTokens * 4;
    if (text.length <= maxChars)
        return text;
    return text.substring(0, maxChars) + '...';
}
// ── Auto-Capture: append to JSONL sidecar ───────────────────────────────
async function captureMemory(text, source, sessionId) {
    try {
        // Dedup check: skip if similar content already in chunks DB
        const similar = await (0, db_1.findSimilarChunks)(text, 3);
        if (similar.length > 0) {
            // Check if any result has very high text overlap (>60% token match)
            const queryTokens = new Set(text.toLowerCase().split(/\W+/).filter(t => t.length > 2));
            for (const chunk of similar) {
                const chunkTokens = new Set((chunk.text || '').toLowerCase().split(/\W+/).filter((t) => t.length > 2));
                const overlap = [...queryTokens].filter(t => chunkTokens.has(t)).length;
                const ratio = overlap / Math.max(queryTokens.size, 1);
                if (ratio > 0.6)
                    return false; // Already known
            }
        }
        const entry = {
            text: text.substring(0, 2000), // Cap at 2000 chars
            timestamp: Date.now(),
            source,
            session_id: sessionId || 'unknown'
        };
        const capturePath = getCapturedPath();
        const dir = path_1.default.dirname(capturePath);
        if (!fs_1.default.existsSync(dir))
            fs_1.default.mkdirSync(dir, { recursive: true });
        fs_1.default.appendFileSync(capturePath, JSON.stringify(entry) + '\n');
        console.log(`${TAG} Captured memory from ${source} (${text.length} chars)`);
        return true;
    }
    catch (e) {
        console.error(`${TAG} Capture failed:`, e.message);
        return false;
    }
}
// ── Extract user messages from hook context ─────────────────────────────
function extractUserMessages(context) {
    const messages = [];
    if (!context)
        return messages;
    // OpenClaw passes context in various shapes — handle the common ones
    if (Array.isArray(context.messages)) {
        for (const msg of context.messages) {
            if (msg.role === 'user' && typeof msg.content === 'string') {
                messages.push(msg.content);
            }
            else if (msg.role === 'user' && Array.isArray(msg.content)) {
                for (const part of msg.content) {
                    if (part.type === 'text')
                        messages.push(part.text);
                }
            }
        }
    }
    if (typeof context.message === 'string') {
        messages.push(context.message);
    }
    if (typeof context.userMessage === 'string') {
        messages.push(context.userMessage);
    }
    if (typeof context.text === 'string') {
        messages.push(context.text);
    }
    return messages.filter(m => m && m.length > 5);
}
function registerHooks(api, config = {}) {
    const enableAutoRecall = config.enableAutoRecall ?? true;
    const enableAutoCapture = config.enableAutoCapture ?? false;
    const enableRulePinning = config.enableRulePinning ?? false;
    // ── before_agent_start: Auto-Recall ──────────────────────────────────
    if (!enableAutoRecall) {
        console.log('[openclaw-memory-max] ⊘ Auto-Recall disabled (opt-in via config.enableAutoRecall).');
    }
    api.on('before_agent_start', async (context) => {
        if (!enableAutoRecall)
            return;
        try {
            const userMessages = extractUserMessages(context);
            if (userMessages.length === 0)
                return;
            const latestMessage = userMessages[userMessages.length - 1];
            if (latestMessage.length < 10)
                return; // Skip trivial messages
            // Stage 1: FTS5 pre-filter
            const candidates = await (0, db_1.searchChunksFTS)(latestMessage, 20);
            if (candidates.length === 0)
                return;
            // Stage 2: Cross-encoder rerank (uses cached model)
            const ranked = await (0, reranker_1.rerankCandidates)(latestMessage, candidates, 5, 0.3);
            if (ranked.length === 0)
                return;
            // Stage 3: Query causal graph for relevant experience
            let experienceXml = '';
            try {
                const chains = await (0, graph_1.queryGraphForHook)(latestMessage, 2);
                if (chains.length > 0) {
                    experienceXml = '\n<relevant-experience>\n' +
                        chains.map(c => `  <chain outcome="${c.outcome}">${c.cause} → ${c.action} → ${c.effect}</chain>`).join('\n') +
                        '\n</relevant-experience>';
                }
            }
            catch {
                // Graph query is optional, don't block recall
            }
            // Build injection block (capped at ~2000 tokens)
            let memoriesXml = '<relevant-memories>\n';
            let tokenBudget = 2000;
            for (const r of ranked) {
                const snippet = truncateTokens(r.text, Math.min(400, tokenBudget));
                const line = `  <memory id="${r.id}" confidence="${r.finalScore.toFixed(2)}" source="${r.source || 'chunks'}">${snippet}</memory>\n`;
                tokenBudget -= Math.ceil(line.length / 4);
                memoriesXml += line;
                if (tokenBudget <= 0)
                    break;
            }
            memoriesXml += '</relevant-memories>';
            // Stage 4: Pinned rules (opt-in only)
            let pinnedXml = '';
            if (enableRulePinning) {
                pinnedXml = (0, weighter_1.buildPinnedRulesXml)();
            }
            const injection = memoriesXml + experienceXml + pinnedXml;
            // Inject into context — OpenClaw supports systemPrompt prepend or context injection
            if (context.addSystemContent) {
                context.addSystemContent(injection);
            }
            else if (context.systemPrompt !== undefined) {
                context.systemPrompt = (context.systemPrompt || '') + '\n\n' + injection;
            }
            else if (context.prependMessages) {
                context.prependMessages([{ role: 'system', content: injection }]);
            }
            console.log(`${TAG} Auto-recall: injected ${ranked.length} memories + ${experienceXml ? 'experience' : 'no experience'}${pinnedXml ? ' + pinned rules' : ''}`);
        }
        catch (e) {
            console.error(`${TAG} Auto-recall failed:`, e.message);
        }
    });
    // ── agent_end: Auto-Capture ──────────────────────────────────────────
    if (!enableAutoCapture) {
        console.log('[openclaw-memory-max] ⊘ Auto-Capture disabled (opt-in via config.enableAutoCapture).');
    }
    api.on('agent_end', async (context) => {
        if (!enableAutoCapture)
            return;
        try {
            const userMessages = extractUserMessages(context);
            let captured = 0;
            for (const msg of userMessages) {
                if (isHighValue(msg)) {
                    const ok = await captureMemory(msg, 'auto-capture', context.sessionId);
                    if (ok)
                        captured++;
                }
            }
            if (captured > 0) {
                console.log(`${TAG} Auto-capture: stored ${captured} high-value messages`);
            }
        }
        catch (e) {
            console.error(`${TAG} Auto-capture failed:`, e.message);
        }
    });
    // ── before_compaction: Memory Rescue ─────────────────────────────────
    api.on('before_compaction', async (context) => {
        if (!enableAutoCapture)
            return;
        try {
            const userMessages = extractUserMessages(context);
            let rescued = 0;
            for (const msg of userMessages) {
                if (isHighValue(msg)) {
                    const ok = await captureMemory(msg, 'compaction-rescue', context.sessionId);
                    if (ok)
                        rescued++;
                }
            }
            if (rescued > 0) {
                console.log(`${TAG} Compaction rescue: saved ${rescued} messages before context eviction`);
            }
        }
        catch (e) {
            console.error(`${TAG} Compaction rescue failed:`, e.message);
        }
    });
    const active = [enableAutoRecall && 'auto-recall', enableAutoCapture && 'auto-capture', enableAutoCapture && 'compaction-rescue'].filter(Boolean).join(', ');
    console.log(`[openclaw-memory-max] ✓ Lifecycle hooks registered (${active || 'all disabled'}).`);
}


path: dist/index.d.ts
content: export interface MemoryMaxConfig {
    enableRulePinning?: boolean;
    enableAutoCapture?: boolean;
    enableAutoRecall?: boolean;
}
declare const memoryMaxPlugin: {
    id: string;
    name: string;
    description: string;
    configSchema: {
        type: string;
        additionalProperties: boolean;
        properties: {
            enableRulePinning: {
                type: string;
                default: boolean;
            };
            enableAutoCapture: {
                type: string;
                default: boolean;
            };
            enableAutoRecall: {
                type: string;
                default: boolean;
            };
        };
    };
    register(api: any): void;
};
export default memoryMaxPlugin;


path: dist/index.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const reranker_1 = require("./reranker");
const weighter_1 = require("./weighter");
const sleep_cycle_1 = require("./sleep-cycle");
const db_1 = require("./db");
const compressor_1 = require("./compressor");
const graph_1 = require("./graph");
const hooks_1 = require("./hooks");
const episodic_1 = require("./episodic");
const memoryMaxPlugin = {
    id: 'openclaw-memory-max',
    name: 'OpenClaw Memory Max (SotA)',
    description: 'SOTA Memory Suite v3: Auto-Recall Hooks, Cross-Encoder Reranking, Multi-Hop Deep Search, Semantic Causal Graph with Dedup/Pruning, Episodic Session Memory, Utility-Weighted Vectors, YAML Rule Pinning, and Nightly Sleep-Cycle Consolidation.',
    configSchema: {
        type: 'object',
        additionalProperties: false,
        properties: {
            enableRulePinning: { type: 'boolean', default: false },
            enableAutoCapture: { type: 'boolean', default: false },
            enableAutoRecall: { type: 'boolean', default: true }
        }
    },
    register(api) {
        console.log('[openclaw-memory-max] Initializing SOTA Memory Cluster v3...');
        // Read plugin config — merge from openclaw.json + api (api takes precedence)
        let fileConfig = {};
        try {
            const fs = require('fs');
            const path = require('path');
            const baseDir = process.env.OPENCLAW_HOME || path.join(process.env.HOME || '/root', '.openclaw');
            const configPath = path.join(baseDir, 'openclaw.json');
            if (fs.existsSync(configPath)) {
                const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
                fileConfig = raw?.plugins?.entries?.['openclaw-memory-max']?.config || {};
            }
        }
        catch { /* fallback silently */ }
        const apiConfig = api.config || api.getConfig?.() || {};
        const config = { ...fileConfig, ...apiConfig };
        const enableRulePinning = config.enableRulePinning ?? false;
        const enableAutoCapture = config.enableAutoCapture ?? false;
        const enableAutoRecall = config.enableAutoRecall ?? true;
        console.log(`[openclaw-memory-max] Config: rulePinning=${enableRulePinning}, autoCapture=${enableAutoCapture}, autoRecall=${enableAutoRecall}`);
        // 0. Ensure Utility Score Schema Exists (async, fire-and-forget)
        (0, db_1.ensureUtilityColumn)().catch((e) => console.error('[openclaw-memory-max] Schema migration failed:', e.message));
        // 1. Cross-Encoder Precision Search + Deep Search + Reward/Penalize
        (0, reranker_1.registerReranker)(api);
        console.log('[openclaw-memory-max] ✓ Precision Reranker + Deep Multi-Hop Search active.');
        // 2. Rule Weighter (read-only — parses MEMORY.md YAML, never writes to global config)
        (0, weighter_1.registerWeighter)(api);
        if (enableRulePinning) {
            console.log('[openclaw-memory-max] ✓ Rule Pinning active (injected via hook, not global config).');
        }
        else {
            console.log('[openclaw-memory-max] ⊘ Rule Pinning disabled (opt-in via config.enableRulePinning).');
        }
        // 3. Context Compressor (wired to before_compaction rescue data)
        (0, compressor_1.registerCompressor)(api);
        console.log('[openclaw-memory-max] ✓ Context Compressor registered.');
        // 4. Causal Knowledge Graph (semantic search, dedup, pruning)
        (0, graph_1.registerCausalGraph)(api);
        console.log('[openclaw-memory-max] ✓ Causal Knowledge Graph live (semantic + dedup).');
        // 5. Lifecycle Hooks: auto-recall, auto-capture, compaction rescue
        (0, hooks_1.registerHooks)(api, { enableAutoCapture, enableAutoRecall, enableRulePinning });
        // 6. Episodic Memory: session segmentation
        (0, episodic_1.registerEpisodic)(api);
        // 7. Sleep Cycle: in-process maintenance scheduler
        (0, sleep_cycle_1.ensureSleepCycle)().catch((e) => console.error('[openclaw-memory-max] Sleep-Cycle setup failed:', e.message));
        console.log('[openclaw-memory-max] All systems nominal. SOTA Memory Matrix ACTIVE.');
    }
};
exports.default = memoryMaxPlugin;


path: dist/reranker.d.ts
content: export declare function getClassifier(): Promise<any>;
/** Score an array of {id, text, utility_score} candidates against a query. Returns sorted desc by finalScore. */
export declare function rerankCandidates(query: string, candidates: any[], topK?: number, threshold?: number): Promise<any[]>;
export declare function registerReranker(api: any): void;


path: dist/reranker.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getClassifier = getClassifier;
exports.rerankCandidates = rerankCandidates;
exports.registerReranker = registerReranker;
const transformers_1 = require("@huggingface/transformers");
const db_1 = require("./db");
function jsonResult(payload) {
    return {
        content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
        details: payload
    };
}
// ── Cross-encoder model singleton ──────────────────────────────────────
let _classifier = null;
async function getClassifier() {
    if (!_classifier) {
        _classifier = await (0, transformers_1.pipeline)('text-classification', 'Xenova/ms-marco-MiniLM-L-6-v2');
    }
    return _classifier;
}
/** Score an array of {id, text, utility_score} candidates against a query. Returns sorted desc by finalScore. */
async function rerankCandidates(query, candidates, topK = 5, threshold = 0.1) {
    if (candidates.length === 0)
        return [];
    const classifier = await getClassifier();
    const firstRow = candidates[0];
    const textKey = Object.keys(firstRow).find(k => typeof firstRow[k] === 'string' && firstRow[k].length > 10) || 'text';
    const scored = [];
    for (const row of candidates) {
        const docText = row[textKey] || '';
        if (!docText)
            continue;
        const output = await classifier(query, docText);
        const semanticScore = output[0]?.score || 0;
        const utilityScore = row.utility_score !== undefined ? row.utility_score : 0.5;
        const finalScore = semanticScore * utilityScore;
        if (finalScore >= threshold) {
            scored.push({
                id: row.id || row.rowid || '',
                text: docText,
                semanticScore,
                utilityScore,
                finalScore,
                path: row.path || '',
                source: row.source || ''
            });
        }
    }
    scored.sort((a, b) => b.finalScore - a.finalScore);
    return scored.slice(0, topK);
}
function registerReranker(api) {
    // 1. Register the Cross-Encoder Search with Utility Math (multi-result)
    api.registerTool({
        name: 'precision_memory_search',
        description: 'Search memory with Cross-Encoder precision and Utility Weighting. Returns the top K most relevant memories ranked by semantic match × utility score.',
        parameters: {
            type: 'object',
            properties: {
                query: { type: 'string', description: 'The exact question or topic to search for.' },
                topK: { type: 'number', description: 'Number of results to return (default 5, max 10).' }
            },
            required: ['query']
        },
        async execute(_toolCallId, args) {
            const topK = Math.min(Math.max(args.topK || 5, 1), 10);
            // Stage 1: FTS5 pre-filter (fast, ~0ms) — falls back to brute-force if FTS unavailable
            let candidates = await (0, db_1.searchChunksFTS)(args.query, 30);
            if (candidates.length === 0) {
                candidates = await (0, db_1.queryChunks)(100);
            }
            if (candidates.length === 0) {
                return jsonResult({ status: 'empty', memory: 'No explicit DB memories found.' });
            }
            // Stage 2+3: Cross-encoder rerank + utility weighting
            const results = await rerankCandidates(args.query, candidates, topK, 0.1);
            if (results.length === 0) {
                return jsonResult({ status: 'no_match', message: 'No memories scored above confidence threshold.' });
            }
            return jsonResult({
                status: 'precision_filtered',
                candidatesScanned: candidates.length,
                retainedCount: results.length,
                results: results.map(r => ({
                    memoryId: r.id,
                    confidence: parseFloat(r.finalScore.toFixed(4)),
                    snippet: r.text.length > 300 ? r.text.substring(0, 300) + '...' : r.text,
                    path: r.path,
                    source: r.source
                }))
            });
        }
    });
    // 2. Register the Explicit Utility Reward Trigger
    api.registerTool({
        name: 'reward_memory_utility',
        description: 'Increments the utility score of a specific memory ID if it proved highly useful to the user. Call this after verifying a memory was successful or highly relevant.',
        parameters: {
            type: 'object',
            properties: {
                memoryId: { type: 'string', description: 'The ID of the memory to reward.' },
                rewardScalar: { type: 'number', description: 'The amount to increment (usually 0.1 to 0.3)' }
            },
            required: ['memoryId']
        },
        async execute(_toolCallId, args) {
            const scalar = args.rewardScalar || 0.1;
            const updated = await (0, db_1.rewardMemory)(args.memoryId, scalar);
            if (updated) {
                return jsonResult({ status: 'success', message: `Rewarded memory ${args.memoryId} by ${scalar}` });
            }
            return jsonResult({ status: 'not_found', message: `Memory ${args.memoryId} not found in any table` });
        }
    });
    // 3. Register the Explicit Utility Penalize Trigger
    api.registerTool({
        name: 'penalize_memory_utility',
        description: 'Decrements the utility score of a specific memory ID if it caused a hallucination or was irrelevant.',
        parameters: {
            type: 'object',
            properties: {
                memoryId: { type: 'string', description: 'The ID of the memory to penalize.' },
                penaltyScalar: { type: 'number', description: 'The amount to decrement (usually -0.1 to -0.3)' }
            },
            required: ['memoryId']
        },
        async execute(_toolCallId, args) {
            const penalty = -(Math.abs(args.penaltyScalar || 0.1));
            const updated = await (0, db_1.rewardMemory)(args.memoryId, penalty);
            if (updated) {
                return jsonResult({ status: 'success', message: `Penalized memory ${args.memoryId} by ${penalty}` });
            }
            return jsonResult({ status: 'not_found', message: `Memory ${args.memoryId} not found in any table` });
        }
    });
    // 4. Deep Multi-Hop Search — chains retrieval → entity extraction → re-retrieval → graph → merge
    api.registerTool({
        name: 'deep_memory_search',
        description: 'Multi-hop memory search. First retrieves memories for the query, extracts key concepts, runs a second search on those concepts, queries the causal graph, and merges all results. Use this for complex questions that might need connecting information across multiple memories.',
        parameters: {
            type: 'object',
            properties: {
                query: { type: 'string', description: 'The question or situation to deeply search for.' }
            },
            required: ['query']
        },
        async execute(_toolCallId, args) {
            // Hop 1: Initial search
            let candidates = await (0, db_1.searchChunksFTS)(args.query, 30);
            if (candidates.length === 0)
                candidates = await (0, db_1.queryChunks)(100);
            const hop1 = await rerankCandidates(args.query, candidates, 5, 0.1);
            // Extract key terms from top results that aren't in the original query
            const queryTokens = new Set(args.query.toLowerCase().split(/\W+/).filter((t) => t.length > 2));
            const extractedTerms = new Set();
            for (const result of hop1.slice(0, 3)) {
                const tokens = result.text.toLowerCase().split(/\W+/).filter((t) => t.length > 3);
                for (const token of tokens) {
                    if (!queryTokens.has(token))
                        extractedTerms.add(token);
                }
            }
            // Hop 2: Search using extracted concepts (top 10 unique terms)
            const hop2Terms = [...extractedTerms].slice(0, 10).join(' ');
            let hop2 = [];
            if (hop2Terms) {
                let hop2Candidates = await (0, db_1.searchChunksFTS)(hop2Terms, 20);
                if (hop2Candidates.length === 0)
                    hop2Candidates = await (0, db_1.queryChunks)(100);
                hop2 = await rerankCandidates(args.query, hop2Candidates, 5, 0.15);
            }
            // Merge + deduplicate
            const seen = new Set();
            const merged = [];
            for (const r of [...hop1, ...hop2]) {
                if (!seen.has(r.id)) {
                    seen.add(r.id);
                    merged.push(r);
                }
            }
            merged.sort((a, b) => b.finalScore - a.finalScore);
            return jsonResult({
                status: 'deep_search_complete',
                hops: { hop1_results: hop1.length, hop2_results: hop2.length, extracted_terms: hop2Terms || '(none)' },
                totalUnique: merged.length,
                results: merged.slice(0, 8).map(r => ({
                    memoryId: r.id,
                    confidence: parseFloat(r.finalScore.toFixed(4)),
                    snippet: r.text.length > 300 ? r.text.substring(0, 300) + '...' : r.text,
                    path: r.path
                }))
            });
        }
    });
}


path: dist/sleep-cycle.d.ts
content: /** Build a summary of recent auto-captures and episodes. */
export declare function buildConsolidationContext(): string;
/**
 * Initialize the sleep cycle system.
 * Runs maintenance on startup + schedules a daily check via setInterval.
 * No child_process — all maintenance runs in-process.
 */
export declare function ensureSleepCycle(): Promise<void>;


path: dist/sleep-cycle.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildConsolidationContext = buildConsolidationContext;
exports.ensureSleepCycle = ensureSleepCycle;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const graph_1 = require("./graph");
const episodic_1 = require("./episodic");
const TAG = '[openclaw-memory-max]';
function getBaseDir() {
    return process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
}
function getScoresPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'utility_scores.json');
}
function getCapturedPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'auto_captured.jsonl');
}
function getConsolidationPath() {
    return path_1.default.join(getBaseDir(), 'memory', 'consolidation_context.md');
}
/** Decay utility scores for memories not accessed recently. */
function decayUtilityScores(decayFactor = 0.99) {
    const scoresPath = getScoresPath();
    if (!fs_1.default.existsSync(scoresPath))
        return 0;
    try {
        const scores = JSON.parse(fs_1.default.readFileSync(scoresPath, 'utf8'));
        let decayed = 0;
        for (const id of Object.keys(scores)) {
            const current = scores[id];
            if (current !== 0.5) {
                scores[id] = parseFloat((current * decayFactor).toFixed(4));
                if (Math.abs(scores[id] - 0.5) < 0.01)
                    scores[id] = 0.5;
                decayed++;
            }
        }
        fs_1.default.writeFileSync(scoresPath, JSON.stringify(scores, null, 2));
        return decayed;
    }
    catch {
        return 0;
    }
}
/** Truncate auto_captured.jsonl entries older than N days. */
function truncateCaptures(days = 30) {
    const capturePath = getCapturedPath();
    if (!fs_1.default.existsSync(capturePath))
        return 0;
    try {
        const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
        const lines = fs_1.default.readFileSync(capturePath, 'utf8').split('\n').filter(Boolean);
        const kept = [];
        let removed = 0;
        for (const line of lines) {
            try {
                const entry = JSON.parse(line);
                if (entry.timestamp >= cutoff) {
                    kept.push(line);
                }
                else {
                    removed++;
                }
            }
            catch {
                removed++;
            }
        }
        fs_1.default.writeFileSync(capturePath, kept.join('\n') + (kept.length > 0 ? '\n' : ''));
        return removed;
    }
    catch {
        return 0;
    }
}
/** Build a summary of recent auto-captures and episodes. */
function buildConsolidationContext() {
    const parts = [];
    const capturePath = getCapturedPath();
    if (fs_1.default.existsSync(capturePath)) {
        const cutoff = Date.now() - (24 * 60 * 60 * 1000);
        const lines = fs_1.default.readFileSync(capturePath, 'utf8').split('\n').filter(Boolean);
        const recent = [];
        for (const line of lines) {
            try {
                const entry = JSON.parse(line);
                if (entry.timestamp >= cutoff) {
                    recent.push(`- [${entry.source}] ${entry.text.substring(0, 200)}`);
                }
            }
            catch { /* skip */ }
        }
        if (recent.length > 0) {
            parts.push(`AUTO-CAPTURED MEMORIES (last 24h, ${recent.length} items):\n${recent.join('\n')}`);
        }
    }
    const episodes = (0, episodic_1.readRecentEpisodes)(7);
    if (episodes.length > 0) {
        const epSummaries = episodes.slice(-5).map(ep => {
            const date = new Date(ep.start).toISOString().split('T')[0];
            return `- [${date}] ${ep.summary || 'No summary'} (tools: ${ep.toolsUsed.join(', ') || 'none'})`;
        });
        parts.push(`RECENT SESSIONS (last 7 days, ${episodes.length} total):\n${epSummaries.join('\n')}`);
    }
    return parts.join('\n\n');
}
/** Run all maintenance tasks: prune graph, decay scores, truncate old data. */
function runMaintenance() {
    try {
        const graphResult = (0, graph_1.pruneGraph)();
        if (graphResult.removed > 0) {
            console.log(`${TAG} Graph pruned: ${graphResult.removed} nodes removed, ${graphResult.remaining} remaining.`);
        }
        const decayed = decayUtilityScores();
        if (decayed > 0) {
            console.log(`${TAG} Utility decay applied to ${decayed} scores.`);
        }
        const capturesRemoved = truncateCaptures(30);
        if (capturesRemoved > 0) {
            console.log(`${TAG} Truncated ${capturesRemoved} old auto-captures.`);
        }
        const episodesRemoved = (0, episodic_1.truncateEpisodes)(30);
        if (episodesRemoved > 0) {
            console.log(`${TAG} Truncated ${episodesRemoved} old episodes.`);
        }
        // Write consolidation context for the next agent session to pick up
        const context = buildConsolidationContext();
        if (context) {
            const contextPath = getConsolidationPath();
            const memDir = path_1.default.dirname(contextPath);
            if (!fs_1.default.existsSync(memDir))
                fs_1.default.mkdirSync(memDir, { recursive: true });
            fs_1.default.writeFileSync(contextPath, `# Memory-Max Consolidation Context\n\nGenerated: ${new Date().toISOString()}\n\n${context}\n`);
        }
    }
    catch (e) {
        console.error(`${TAG} Maintenance tasks failed:`, e.message);
    }
}
/** Track last maintenance run to avoid running more than once per 20h. */
let lastMaintenanceRun = 0;
function runMaintenanceIfDue() {
    const now = Date.now();
    const twentyHours = 20 * 60 * 60 * 1000;
    if (now - lastMaintenanceRun < twentyHours)
        return;
    lastMaintenanceRun = now;
    runMaintenance();
}
/**
 * Initialize the sleep cycle system.
 * Runs maintenance on startup + schedules a daily check via setInterval.
 * No child_process — all maintenance runs in-process.
 */
function ensureSleepCycle() {
    console.log(`${TAG} Initializing Sleep Cycle (in-process scheduler)...`);
    // Run maintenance immediately on startup
    runMaintenanceIfDue();
    // Schedule periodic check — runs every 6 hours, but maintenance
    // only actually executes once per 20 hours (dedup guard above)
    const SIX_HOURS = 6 * 60 * 60 * 1000;
    setInterval(() => {
        runMaintenanceIfDue();
    }, SIX_HOURS);
    console.log(`${TAG} Sleep Cycle active (maintenance every ~24h, next check in 6h).`);
    return Promise.resolve();
}


path: dist/weighter.d.ts
content: /**
 * Parse YAML rule blocks from MEMORY.md and return rules with weight >= 1.0.
 * Reads at most once per 15 seconds (cached).
 */
export declare function getPinnedRules(): string[];
/**
 * Build an XML block of pinned rules for context injection.
 * Returns empty string if no rules are pinned.
 */
export declare function buildPinnedRulesXml(): string;
/**
 * Register the weighter module. No longer writes to openclaw.json —
 * pinned rules are injected via the before_agent_start hook instead.
 */
export declare function registerWeighter(_api: any): void;


path: dist/weighter.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPinnedRules = getPinnedRules;
exports.buildPinnedRulesXml = buildPinnedRulesXml;
exports.registerWeighter = registerWeighter;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const yaml_1 = __importDefault(require("yaml"));
const TAG = '[openclaw-memory-max][weighter]';
let _pinnedRules = [];
let _lastRead = 0;
function getMemoryPath() {
    const baseDir = process.env.OPENCLAW_HOME || path_1.default.join(process.env.HOME || '/root', '.openclaw');
    return path_1.default.join(baseDir, 'memory/MEMORY.md');
}
/**
 * Parse YAML rule blocks from MEMORY.md and return rules with weight >= 1.0.
 * Reads at most once per 15 seconds (cached).
 */
function getPinnedRules() {
    const now = Date.now();
    if (now - _lastRead < 15000 && _pinnedRules.length >= 0)
        return _pinnedRules;
    _lastRead = now;
    try {
        const memoryPath = getMemoryPath();
        if (!fs_1.default.existsSync(memoryPath))
            return _pinnedRules = [];
        const text = fs_1.default.readFileSync(memoryPath, 'utf8');
        const match = text.match(/<!--yaml\n([\s\S]*?)\n-->/);
        if (!match)
            return _pinnedRules = [];
        const parsed = yaml_1.default.parse(match[1]);
        if (!parsed?.rules || !Array.isArray(parsed.rules))
            return _pinnedRules = [];
        _pinnedRules = parsed.rules
            .filter((r) => parseFloat(r.weight) >= 1.0)
            .map((r) => r.constraint || r.rule || r.preference)
            .filter(Boolean);
        return _pinnedRules;
    }
    catch {
        return _pinnedRules = [];
    }
}
/**
 * Build an XML block of pinned rules for context injection.
 * Returns empty string if no rules are pinned.
 */
function buildPinnedRulesXml() {
    const rules = getPinnedRules();
    if (rules.length === 0)
        return '';
    return '\n<pinned-constraints>\n' +
        rules.map(r => `  <constraint weight="1.0">${r}</constraint>`).join('\n') +
        '\n</pinned-constraints>';
}
/**
 * Register the weighter module. No longer writes to openclaw.json —
 * pinned rules are injected via the before_agent_start hook instead.
 */
function registerWeighter(_api) {
    // Pre-warm the cache on startup
    const rules = getPinnedRules();
    if (rules.length > 0) {
        console.log(`${TAG} Loaded ${rules.length} pinned rule(s) from MEMORY.md.`);
    }
    else {
        console.log(`${TAG} No pinned rules found (add <!--yaml ... --> block to MEMORY.md).`);
    }
}


path: openclaw.plugin.json
content: {
    "id": "openclaw-memory-max",
    "name": "OpenClaw Memory Max",
    "version": "3.0.3",
    "description": "SOTA Memory Suite: Auto-Recall Hooks, Cross-Encoder Reranking, Multi-Hop Deep Search, Semantic Causal Graph, Episodic Memory, Utility Vectors, and Nightly Sleep-Cycle Consolidation.",
    "kind": "memory",
    "main": "dist/index.js",
    "configSchema": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
            "enableRulePinning": {
                "type": "boolean",
                "default": false,
                "description": "Enable YAML rule pinning from MEMORY.md into the system prompt. Off by default — must be explicitly opted in. Only enable if you control write access to MEMORY.md."
            },
            "enableAutoCapture": {
                "type": "boolean",
                "default": false,
                "description": "Enable automatic capture of high-value user messages (rules, corrections, preferences) to sidecar files. Off by default — opt in if you want persistent message logging."
            },
            "enableAutoRecall": {
                "type": "boolean",
                "default": true,
                "description": "Enable automatic memory injection before each agent turn via lifecycle hooks."
            }
        }
    },
    "env": {
        "OPENCLAW_HOME": {
            "description": "Base directory for OpenClaw data. Defaults to ~/.openclaw if not set.",
            "required": false
        }
    },
    "fileAccess": {
        "reads": [
            "$OPENCLAW_HOME/main.sqlite — Read-only access to the memory database (chunks table for search)",
            "$OPENCLAW_HOME/openclaw.json — Read plugin config from the global config file",
            "$OPENCLAW_HOME/memory/MEMORY.md — Read YAML rule blocks (only when enableRulePinning=true)"
        ],
        "writes": [
            "$OPENCLAW_HOME/memory/utility_scores.json — Reinforcement scores per memory ID (~100 KB)",
            "$OPENCLAW_HOME/memory/causal_graph.json — Cause-action-effect chains, 500 node cap (~500 KB)",
            "$OPENCLAW_HOME/memory/auto_captured.jsonl — Auto-captured messages, 30-day cap (only when enableAutoCapture=true)",
            "$OPENCLAW_HOME/memory/episodes.jsonl — Session episode summaries, 30-day cap (~1 MB)",
            "$OPENCLAW_HOME/memory/consolidation_context.md — Sleep cycle consolidation summary"
        ]
    },
    "privacy": {
        "storesUserMessages": "opt-in (enableAutoCapture=false by default)",
        "modifiesSystemPrompt": "auto-recall injects <relevant-memories> into session context via hook (not global config); rule pinning is opt-in (enableRulePinning=false by default) and also injected via hook only",
        "modifiesGlobalConfig": false,
        "externalNetworkAccess": false,
        "dataRetention": "sidecar files auto-truncated after 30 days by in-process sleep cycle"
    }
}


path: package-lock.json
content: {
  "name": "openclaw-memory-max",
  "version": "1.1.1",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "openclaw-memory-max",
      "version": "1.1.1",
      "license": "MIT",
      "dependencies": {
        "@huggingface/transformers": "^3.8.0",
        "sql.js": "^1.14.0",
        "yaml": "^2.4.1"
      },
      "devDependencies": {
        "@types/node": "^18.19.0",
        "typescript": "^5.3.0"
      }
    },
    "node_modules/@emnapi/runtime": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "optional": true,
      "dependencies": {
        "tslib": "^2.4.0"
      }
    },
    "node_modules/@huggingface/transformers": {
      "version": "3.8.1",
      "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-3.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "dependencies": {
        "@huggingface/jinja": "^0.5.3",
        "onnxruntime-node": "1.21.0",
        "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4",
        "sharp": "^0.34.1"
      }
    },
    "node_modules/@huggingface/transformers/node_modules/@huggingface/jinja": {
      "version": "0.5.6",
      "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@huggingface/transformers/node_modules/flatbuffers": {
      "version": "25.9.23",
      "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0"
    },
    "node_modules/@huggingface/transformers/node_modules/long": {
      "version": "5.3.2",
      "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0"
    },
    "node_modules/@huggingface/transformers/node_modules/onnxruntime-common": {
      "version": "1.21.0",
      "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@huggingface/transformers/node_modules/onnxruntime-node": {
      "version": "1.21.0",
      "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.21.0.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "MIT",
      "os": [
        "win32",
        "darwin",
        "linux"
      ],
      "dependencies": {
        "global-agent": "^3.0.0",
        "onnxruntime-common": "1.21.0",
        "tar": "^7.0.1"
      }
    },
    "node_modules/@huggingface/transformers/node_modules/onnxruntime-web": {
      "version": "1.22.0-dev.20250409-89f8206ba4",
      "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.22.0-dev.20250409-89f8206ba4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "flatbuffers": "^25.1.24",
        "guid-typescript": "^1.0.9",
        "long": "^5.2.3",
        "onnxruntime-common": "1.22.0-dev.20250409-89f8206ba4",
        "platform": "^1.3.6",
        "protobufjs": "^7.2.4"
      }
    },
    "node_modules/@huggingface/transformers/node_modules/onnxruntime-web/node_modules/onnxruntime-common": {
      "version": "1.22.0-dev.20250409-89f8206ba4",
      "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.22.0-dev.20250409-89f8206ba4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@huggingface/transformers/node_modules/protobufjs": {
      "version": "7.5.4",
      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "BSD-3-Clause",
      "dependencies": {
        "@protobufjs/aspromise": "^1.1.2",
        "@protobufjs/base64": "^1.1.2",
        "@protobufjs/codegen": "^2.0.4",
        "@protobufjs/eventemitter": "^1.1.0",
        "@protobufjs/fetch": "^1.1.0",
        "@protobufjs/float": "^1.0.2",
        "@protobufjs/inquire": "^1.1.0",
        "@protobufjs/path": "^1.1.2",
        "@protobufjs/pool": "^1.1.0",
        "@protobufjs/utf8": "^1.1.0",
        "@types/node": ">=13.7.0",
        "long": "^5.0.0"
      },
      "engines": {
        "node": ">=12.0.0"
      }
    },
    "node_modules/@huggingface/transformers/node_modules/sharp": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "hasInstallScript": true,
      "license": "Apache-2.0",
      "dependencies": {
        "@img/colour": "^1.0.0",
        "detect-libc": "^2.1.2",
        "semver": "^7.7.3"
      },
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-darwin-arm64": "0.34.5",
        "@img/sharp-darwin-x64": "0.34.5",
        "@img/sharp-libvips-darwin-arm64": "1.2.4",
        "@img/sharp-libvips-darwin-x64": "1.2.4",
        "@img/sharp-libvips-linux-arm": "1.2.4",
        "@img/sharp-libvips-linux-arm64": "1.2.4",
        "@img/sharp-libvips-linux-ppc64": "1.2.4",
        "@img/sharp-libvips-linux-riscv64": "1.2.4",
        "@img/sharp-libvips-linux-s390x": "1.2.4",
        "@img/sharp-libvips-linux-x64": "1.2.4",
        "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
        "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
        "@img/sharp-linux-arm": "0.34.5",
        "@img/sharp-linux-arm64": "0.34.5",
        "@img/sharp-linux-ppc64": "0.34.5",
        "@img/sharp-linux-riscv64": "0.34.5",
        "@img/sharp-linux-s390x": "0.34.5",
        "@img/sharp-linux-x64": "0.34.5",
        "@img/sharp-linuxmusl-arm64": "0.34.5",
        "@img/sharp-linuxmusl-x64": "0.34.5",
        "@img/sharp-wasm32": "0.34.5",
        "@img/sharp-win32-arm64": "0.34.5",
        "@img/sharp-win32-ia32": "0.34.5",
        "@img/sharp-win32-x64": "0.34.5"
      }
    },
    "node_modules/@img/colour": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@img/sharp-darwin-arm64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-darwin-arm64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-darwin-x64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-darwin-x64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-libvips-darwin-arm64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "darwin"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-darwin-x64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "darwin"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-arm": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-arm64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-ppc64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-riscv64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-s390x": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linux-x64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-libvips-linuxmusl-x64": {
      "version": "1.2.4",
      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "linux"
      ],
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-linux-arm": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-arm": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linux-arm64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-arm64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linux-ppc64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-ppc64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linux-riscv64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-riscv64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linux-s390x": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-s390x": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linux-x64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linux-x64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linuxmusl-arm64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-linuxmusl-x64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "Apache-2.0",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      },
      "optionalDependencies": {
        "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
      }
    },
    "node_modules/@img/sharp-wasm32": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "wasm32"
      ],
      "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
      "optional": true,
      "dependencies": {
        "@emnapi/runtime": "^1.7.0"
      },
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-win32-arm64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "license": "Apache-2.0 AND LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-win32-ia32": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "license": "Apache-2.0 AND LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@img/sharp-win32-x64": {
      "version": "0.34.5",
      "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "license": "Apache-2.0 AND LGPL-3.0-or-later",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/libvips"
      }
    },
    "node_modules/@isaacs/fs-minipass": {
      "version": "4.0.1",
      "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "dependencies": {
        "minipass": "^7.0.4"
      },
      "engines": {
        "node": ">=18.0.0"
      }
    },
    "node_modules/@protobufjs/aspromise": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/base64": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/codegen": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/eventemitter": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/fetch": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "@protobufjs/aspromise": "^1.1.1",
        "@protobufjs/inquire": "^1.1.0"
      }
    },
    "node_modules/@protobufjs/float": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/inquire": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/path": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/pool": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@protobufjs/utf8": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/@types/node": {
      "version": "18.19.0",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "undici-types": "~5.26.4"
      }
    },
    "node_modules/boolean": {
      "version": "3.2.0",
      "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
      "license": "MIT"
    },
    "node_modules/define-data-property": {
      "version": "1.1.4",
      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-define-property": "^1.0.0",
        "es-errors": "^1.3.0",
        "gopd": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/define-properties": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "define-data-property": "^1.0.1",
        "has-property-descriptors": "^1.0.0",
        "object-keys": "^1.1.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/detect-libc": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "Apache-2.0",
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/detect-node": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es6-error": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/escape-string-regexp": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/global-agent": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "boolean": "^3.0.1",
        "es6-error": "^4.1.1",
        "matcher": "^3.0.0",
        "roarr": "^2.15.3",
        "semver": "^7.3.2",
        "serialize-error": "^7.0.1"
      },
      "engines": {
        "node": ">=10.0"
      }
    },
    "node_modules/globalthis": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "define-properties": "^1.2.1",
        "gopd": "^1.0.1"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/guid-typescript": {
      "version": "1.0.9",
      "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/has-property-descriptors": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-define-property": "^1.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/json-stringify-safe": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/matcher": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "escape-string-regexp": "^4.0.0"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/minipass": {
      "version": "7.1.3",
      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=16 || 14 >=14.17"
      }
    },
    "node_modules/minizlib": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "minipass": "^7.1.2"
      },
      "engines": {
        "node": ">= 18"
      }
    },
    "node_modules/object-keys": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/platform": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/roarr": {
      "version": "2.15.4",
      "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause",
      "dependencies": {
        "boolean": "^3.0.1",
        "detect-node": "^2.0.4",
        "globalthis": "^1.0.1",
        "json-stringify-safe": "^5.0.1",
        "semver-compare": "^1.0.0",
        "sprintf-js": "^1.1.2"
      },
      "engines": {
        "node": ">=8.0"
      }
    },
    "node_modules/semver": {
      "version": "7.7.4",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/semver-compare": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/serialize-error": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "type-fest": "^0.13.1"
      },
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/sprintf-js": {
      "version": "1.1.3",
      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BSD-3-Clause"
    },
    "node_modules/sql.js": {
      "version": "1.14.1",
      "resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/tar": {
      "version": "7.5.11",
      "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "dependencies": {
        "@isaacs/fs-minipass": "^4.0.0",
        "chownr": "^3.0.0",
        "minipass": "^7.1.2",
        "minizlib": "^3.1.0",
        "yallist": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/tar/node_modules/chownr": {
      "version": "3.0.0",
      "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/tslib": {
      "version": "2.8.1",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "0BSD",
      "optional": true
    },
    "node_modules/type-fest": {
      "version": "0.13.1",
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "(MIT OR CC0-1.0)",
      "engines": {
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/typescript": {
      "version": "5.9.3",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "Apache-2.0",
      "bin": {
        "tsc": "bin/tsc",
        "tsserver": "bin/tsserver"
      },
      "engines": {
        "node": ">=14.17"
      }
    },
    "node_modules/undici-types": {
      "version": "5.26.5",
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/yallist": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "BlueOak-1.0.0",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/yaml": {
      "version": "2.8.2",
      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "bin": {
        "yaml": "bin.mjs"
      },
      "engines": {
        "node": ">= 14.6"
      },
      "funding": {
        "url": "https://github.com/sponsors/eemeli"
      }
    }
  }
}


path: package.json
content: {
  "name": "openclaw-memory-max",
  "version": "3.0.3",
  "description": "SOTA Memory Suite for OpenClaw. Adds Utility-Weighted Retrieval, Active Context Compression, a Causal Knowledge Graph, Cross-Encoder Reranking, and Nightly Sleep-Cycle consolidation.",
  "main": "dist/index.js",
  "keywords": [
    "openclaw",
    "memory",
    "ai",
    "rag",
    "knowledge-graph",
    "context-compression"
  ],
  "author": "Stanislav Stolberg",
  "license": "MIT",
  "type": "commonjs",
  "scripts": {
    "build": "tsc",
    "audit": "node -e \"const path = require('path'); const fs = require('fs'); const baseDir = process.env.OPENCLAW_HOME || path.join(process.env.HOME || '/root', '.openclaw'); const memDir = path.join(baseDir, 'memory'); const dbPath = path.join(memDir, 'main.sqlite'); const scoresPath = path.join(memDir, 'utility_scores.json'); const graphPath = path.join(memDir, 'causal_graph.json'); console.log('main.sqlite present:', fs.existsSync(dbPath)); console.log('utility_scores.json present:', fs.existsSync(scoresPath)); console.log('causal_graph.json present:', fs.existsSync(graphPath));\"",
    "test": "tsc --noEmit && node -e \"require('./dist/index.js'); console.log('[test] Entry point loaded successfully');\""
  },
  "dependencies": {
    "@huggingface/transformers": "^3.8.0",
    "sql.js": "^1.14.0",
    "yaml": "^2.4.1"
  },
  "devDependencies": {
    "@types/node": "^18.19.0",
    "typescript": "^5.3.0"
  },
  "openclaw": {
    "type": "extension",
    "extensions": [
      "./dist/index.js"
    ]
  }
}


path: src/compressor.ts
content: import fs from 'fs';
import path from 'path';

function getBaseDir(): string {
    return process.env.OPENCLAW_HOME || path.join(process.env.HOME || '/root', '.openclaw');
}

function getCapturedPath(): string {
    return path.join(getBaseDir(), 'memory', 'auto_captured.jsonl');
}

function jsonResult(payload: any) {
    return {
        content: [{ type: 'text' as const, text: JSON.stringify(payload, null, 2) }],
        details: payload
    };
}

// Track what the before_compaction hook rescued
let lastCompactionRescue: { count: number; timestamp: number; items: string[] } = { count: 0, timestamp: 0, items: [] };

/** Called by the before_compaction hook in hooks.ts to report rescued items. */
export function reportCompactionRescue(count: number, items: string[]): void {
    lastCompactionRescue = { count, timestamp: Date.now(), items: items.slice(0, 5) };
}

export function registerCompressor(api: any) {
    api.registerTool({
        name: 'compress_context',
        description: 'Signal that context compression is needed and review what was rescued from the last compaction. Call when the context window feels overloaded. Returns rescued memories from recent compaction events plus an advisory to the runtime.',
        parameters: {
            type: 'object',
            properties: {
                compression_reason: { type: 'string', description: 'Why you decided to compress the context now.' }
            },
            required: ['compression_reason']
        },
        async execute(_toolCallId: string, args: any) {
            // Read recent auto-captured entries (last 24h) for context
            const recentCaptures: string[] = [];
            try {
                const capturePath = getCapturedPath();
                if (fs.existsSync(capturePath)) {
                    const cutoff = Date.now() - (24 * 60 * 60 * 1000);
                    const lines = fs.readFileSync(capturePath, 'utf8').split('\n').filter(Boolean);
                    for (const line of lines) {
                        try {
                            const entry = JSON.parse(line);
                            if (entry.timestamp >= cutoff) {
                                recentCaptures.push(`[${entry.source}] ${entry.text.substring(0, 100)}`);
                            }
                        } catch { /* skip */ }
                    }
                }
            } catch { /* non-critical */ }

            return jsonResult({
                status: 'compression_advisory',
                reason: args.compression_reason,
                message: 'Context compression requested. The runtime handles actual eviction. High-value content is auto-rescued by the before_compaction hook.',
                lastRescue: lastCompactionRescue.count > 0 ? {
                    rescued: lastCompactionRescue.count,
                    when: new Date(lastCompactionRescue.timestamp).toISOString(),
                    preview: lastCompactionRescue.items
                } : null,
                recentAutoCaptures: recentCaptures.length,
                capturePreview: recentCaptures.slice(0, 3)
            });
        }
    });
}


path: src/db.ts
content: import path from 'path';
import fs from 'fs';
import initSqlJs, { Database as SqlJsDatabase } from 'sql.js';

let _SQL: any = null;

function getBaseDir(): string {
    return process.env.OPENCLAW_HOME || path.join(process.env.HOME || '/root', '.openclaw');
}

function getDbPath(): string {
    return path.join(getBaseDir(), 'memory', 'main.sqlite');
}

function getScoresPath(): string {
    return path.join(getBaseDir(), 'memory', 'utility_scores.json');
}

async function getSqlJs() {
    if (!_SQL) _SQL = await initSqlJs();
    return _SQL;
}

// ── Read-only access to OpenClaw's main.sqlite ──────────────────────────

/** Open a read-only snapshot. Caller must close(). */
async function openDb(): Promise<SqlJsDatabase | null> {
    const SQL = await getSqlJs();
    const dbPath = getDbPath();
    if (!fs.existsSync(dbPath)) return null;
    const buffer = fs.readFileSync(dbPath);
    return new SQL.Database(buffer);
}

function queryAll(db: SqlJsDatabase, sql: string, params?: any[]): any[] {
    const stmt = db.prepare(sql);
    if (params) stmt.bind(params);
    const rows: any[] = [];
    while (stmt.step()) {
        rows.push(stmt.getAsObject());
    }
    stmt.free();
    return rows;
}

// ── Plugin-owned utility scores (JSON sidecar) ─────────────────────────

type ScoreMap = Record<string, number>;

function loadScores(): ScoreMap {
    const p = getScoresPath();
    if (!fs.existsSync(p)) return {};
    try {
        return JSON.parse(fs.readFileSync(p, 'utf8'));
    } catch {
        return {};
    }
}

function saveScores(scores: ScoreMap): void {
    const p = getScoresPath();
    const dir = path.dirname(p);
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(p, JSON.stringify(scores, null, 2));
}

/** Get utility score for a memory ID (default 0.5). */
export function getUtilityScore(id: string): number {
    const scores = loadScores();
    return scores[id] !== undefined ? scores[id] : 0.5;
}

// ── FTS5 Pre-filter + Dedup ─────────────────────────────────────────────

/** Fast BM25 pre-filter via FTS5. Falls back to keyword matching if FTS5 unavailable. */
export async function searchChunksFTS(query: string, limit: number = 20): Promise<any[]> {
    const db = await openDb();
    if (!db) return [];

    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table' OR type='virtual'");
        const hasFTS = tables.some((t: any) => t.name === 'chunks_fts');

        let rows: any[];
        if (hasFTS) {
            // FTS5 MATCH — sanitize query for FTS syntax (wrap tokens in quotes)
            const sanitized = query.replace(/[^\w\s]/g, '').split(/\s+/).filter(Boolean).map(t => `"${t}"`).join(' OR ');
            if (!sanitized) { db.close(); return []; }
            try {
                rows = queryAll(db, `SELECT chunks_fts.id, chunks_fts.text, chunks_fts.path, chunks_fts.source, rank
                    FROM chunks_fts
                    WHERE chunks_fts MATCH ?
                    ORDER BY rank
                    LIMIT ?`, [sanitized, limit]);
            } catch {
                // FTS5 might not be compiled into sql.js — fall back
                rows = [];
            }
        } else {
            rows = [];
        }

        // Fallback: keyword pre-filter in JS if FTS5 unavailable or returned nothing
        if (rows.length === 0) {
            const allChunks = queryAll(db, `SELECT * FROM chunks ORDER BY rowid DESC LIMIT 200`);
            const queryTokens = new Set(query.toLowerCase().split(/\W+/).filter(t => t.length > 2));
            rows = allChunks.filter((row: any) => {
                const text = (row.text || '').toLowerCase();
                let matches = 0;
                for (const token of queryTokens) {
                    if (text.includes(token)) matches++;
                }
                return matches >= Math.min(2, queryTokens.size);
            }).slice(0, limit);
        }

        db.close();

        // Enrich with utility scores
        const scores = loadScores();
        for (const row of rows) {
            const id = row.id || row.rowid || '';
            row.utility_score = scores[id] !== undefined ? scores[id] : 0.5;
        }

        return rows;
    } catch (e: any) {
        db.close();
        console.error('[openclaw-memory-max][db] FTS search failed:', e.message);
        return [];
    }
}

/** Check for duplicate content. Returns chunks with high text overlap. */
export async function findSimilarChunks(text: string, limit: number = 5): Promise<any[]> {
    // Use FTS5 with the first ~50 words of the text as query
    const queryText = text.split(/\s+/).slice(0, 50).join(' ');
    return searchChunksFTS(queryText, limit);
}

// ── Public API ──────────────────────────────────────────────────────────

/** One-time schema check — read-only, no writes to main.sqlite. */
export async function ensureUtilityColumn() {
    const db = await openDb();
    if (!db) {
        console.log('[openclaw-memory-max][db] DB file not found. Awaiting context instantiation.');
        return;
    }

    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
        console.log('[openclaw-memory-max][db] Auditing tables:', tables.map((t: any) => t.name).join(', '));

        const hasChunks = tables.some((t: any) => t.name === 'chunks');
        if (hasChunks) {
            console.log('[openclaw-memory-max][db] Memory table found. Utility scores stored in sidecar file.');
        } else {
            console.log('[openclaw-memory-max][db] Memory table not found. Awaiting context instantiation.');
        }

        db.close();
    } catch (e: any) {
        db.close();
        console.error('[openclaw-memory-max][db] SQLite Audit Failed:', e.message);
    }
}

/** Update utility score in the sidecar file. Validates the ID exists in main.sqlite first. */
export async function rewardMemory(id: string, scalar: number = 0.1): Promise<boolean> {
    try {
        // Verify the memory ID actually exists in main.sqlite
        const db = await openDb();
        if (!db) return false;

        let found = false;
        try {
            const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
            const targets = ['chunks', 'memories', 'documents', 'episodic_memory'];
            for (const tbl of targets) {
                if (!tables.some((t: any) => t.name === tbl)) continue;
                const rows = queryAll(db, `SELECT 1 FROM ${tbl} WHERE id = ? LIMIT 1`, [id]);
                if (rows.length > 0) { found = true; break; }
            }
        } finally {
            db.close();
        }

        if (!found) return false;

        const scores = loadScores();
        const current = scores[id] !== undefined ? scores[id] : 0.5;
        const updated = Math.max(0.0, Math.min(1.0, current + scalar));
        scores[id] = updated;
        saveScores(scores);
        console.log(`[openclaw-memory-max][db] Updated utility for ${id}: ${current.toFixed(2)} → ${updated.toFixed(2)}`);
        return true;
    } catch (e: any) {
        console.error('[openclaw-memory-max][db] Failed to update utility score:', e.message);
        return false;
    }
}

/** Query chunks table — read-only from main.sqlite, enriched with sidecar scores. */
export async function queryChunks(limit: number = 100): Promise<any[]> {
    const db = await openDb();
    if (!db) return [];

    try {
        const tables = queryAll(db, "SELECT name FROM sqlite_master WHERE type='table'");
        const hasChunks = tables.some((t: any) => t.name === 'chunks');
        if (!hasChunks) {
            db.close();
            return [];
        }
        const rows = queryAll(db, `SELECT * FROM chunks ORDER BY rowid DESC LIMIT ?`, [limit]);
        db.close();

        // Enrich with sidecar utility scores
        const scores = loadScores();
        for (const row of rows) {
            const id = row.id || row.rowid || '';
            row.utility_score = scores[id] !== undefined ? scores[id] : 0.5;
        }

        return rows;
    } catch (e: any) {
        db.close();
        console.error('[openclaw-memory-max][db] Failed to query chunks:', e.message);
        return [];
    }
}


path: src/weighter.ts
content: import fs from 'fs';
import path from 'path';
import yaml from 'yaml';

const TAG = '[openclaw-memory-max][weighter]';

let _pinnedRules: string[] = [];
let _lastRead = 0;

function getMemoryPath(): string {
    const baseDir = process.env.OPENCLAW_HOME || path.join(process.env.HOME || '/root', '.openclaw');
    return path.join(baseDir, 'memory/MEMORY.md');
}

/**
 * Parse YAML rule blocks from MEMORY.md and return rules with weight >= 1.0.
 * Reads at most once per 15 seconds (cached).
 */
export function getPinnedRules(): string[] {
    const now = Date.now();
    if (now - _lastRead < 15000 && _pinnedRules.length >= 0) return _pinnedRules;
    _lastRead = now;

    try {
        const memoryPath = getMemoryPath();
        if (!fs.existsSync(memoryPath)) return _pinnedRules = [];

        const text = fs.readFileSync(memoryPath, 'utf8');
        const match = text.match(/<!--yaml\n([\s\S]*?)\n-->/);
        if (!match) return _pinnedRules = [];

        const parsed = yaml.parse(match[1]);
        if (!parsed?.rules || !Array.isArray(parsed.rules)) return _pinnedRules = [];

        _pinnedRules = parsed.rules
            .filter((r: any) => parseFloat(r.weight) >= 1.0)
            .map((r: any) => r.constraint || r.rule || r.preference)
            .filter(Boolean);

        return _pinnedRules;
    } catch {
        return _pinnedRules = [];
    }
}

/**
 * Build an XML block of pinned rules for context injection.
 * Returns empty string if no rules are pinned.
 */
export function buildPinnedRulesXml(): string {
    const rules = getPinnedRules();
    if (rules.length === 0) return '';

    return '\n<pinned-constraints>\n' +
        rules.map(r => `  <constraint weight="1.0">${r}</constraint>`).join('\n') +
        '\n</pinned-constraints>';
}

/**
 * Register the weighter module. No longer writes to openclaw.json —
 * pinned rules are injected via the before_agent_start hook instead.
 */
export function registerWeighter(_api: any) {
    // Pre-warm the cache on startup
    const rules = getPinnedRules();
    if (rules.length > 0) {
        console.log(`${TAG} Loaded ${rules.length} pinned rule(s) from MEMORY.md.`);
    } else {
        console.log(`${TAG} No pinned rules found (add <!--yaml ... --> block to MEMORY.md).`);
    }
}


path: tsconfig.json
content: {
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "types": ["node"],
    "moduleResolution": "node",
    "outDir": "dist",
    "rootDir": "src",
    "strict": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "resolveJsonModule": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)