feat(mem0-plugin): file-context injection, stop-hook summaries & activity timeline (#5346)

This commit is contained in:
Kartik
2026-06-03 00:55:07 +05:30
committed by GitHub
parent 8e65ce915d
commit 74771b4e76
24 changed files with 989 additions and 29 deletions
+47
View File
@@ -0,0 +1,47 @@
# Changelog
All notable changes to the `@mem0/opencode-plugin` will be documented in this file.
## 0.1.3 — File-context injection, session summaries & activity timeline
### Added
- **File-context injection (`tool.execute.before` / Read):** Before the agent reads a file, the plugin searches mem0 for memories referencing that file path and injects prior work as system context. Gates on file size (>= 1,500 bytes). Gives the agent "I've worked on this file before" awareness automatically.
- **Stop hook session summary (`experimental.session.compacting`):** Enhanced session compaction to store a structured `session_summary` memory with `infer=True`, letting the mem0 backend AI extract key facts (request, decisions, learnings, next steps). Previously only stored a raw stats string.
- **SessionStart activity timeline:** The initial memory loading now formats recent memories with type icons (⚖️ decision, 🔴 bug_fix, 🔵 task_learning, etc.) and relative age indicators (2h ago, 1d ago) instead of bare text. Provides a visual "Recent Activity" timeline on first message.
### Changed
- **`experimental.session.compacting` handler:** Now stores `metadata.type=session_summary` with `metadata.source=opencode-stop` instead of `metadata.type=session_state` with `metadata.source=pre-compaction`. Includes a structured prompt that instructs mem0's AI to extract request, decisions, learnings, and next steps.
- **Initial context formatting:** Memories shown on first message now include type icons and age labels for quick scanning.
## 0.1.2 — Automatic coding categories & global search
### Added
- **Auto-configured coding categories:** The plugin now automatically sets up 17 coding categories (e.g. `architecture_decisions`, `api_design`, `security`, `debugging_notes`) on the Mem0 project at startup. Runs in the background on every session start via `autoSetupCategories()`, is fully idempotent, and never blocks initialization. Uses SHA-256 fingerprints of the category list and API key — stored in `~/.mem0/categories_setup.json` — to skip redundant API calls on subsequent sessions.
- **Global search mode (`global_search` setting):** New `global_search` toggle in `~/.mem0/settings.json` (default: `false`). When enabled, all `search_memories` and `get_memories` calls use `{"OR": [{"user_id": "*"}]}` instead of the per-user per-project `AND` filter — returning all memories across all users and all `app_id` scopes. Writes (`add_memory`) still tag with the current `user_id` and `app_id`. Applies to all plugin search paths: initial load, per-message recall, resume detection, error-pattern lookup, and compaction context.
- **`/mem0:switch-project --global` / `--no-global`:** Enables or disables global search via the switch-project skill. Persists to `~/.mem0/settings.json`. No manual config editing needed.
- **`MEM0_GLOBAL_SEARCH` environment variable:** Exported to child shells via the `shell.env` hook (`"true"` or `"false"`).
### Changed
- **Search filters are now dynamic:** All search paths throughout the plugin construct filters based on the `global_search` setting instead of always using `AND [user_id, app_id]`.
- **Resume-context searches broadened:** Resume and error-pattern searches no longer include `metadata.type` sub-filters (`session_state`, `decision`, `anti_pattern`, `bug_fix`), broadening recall.
- **System context message updated:** Informs the model when global search is active (`"Global search is ON — searches return all memories across all users and projects. Writes still use user_id=..., app_id=..."`).
- **`/mem0:onboard` Step 5 is no longer interactive:** Removed the manual category installation prompt. Categories now configure automatically in the background; the onboarding step only verifies status and stores a fallback `project_profile` memory if the background run hasn't finished yet.
- **`/mem0:switch-project` skill expanded:** Description and execution updated to document the `--global` and `--no-global` flags alongside the existing project-name argument.
## 0.1.1
- CI/CD publish flow test (`#5288`).
- Fixed tsconfig, added `publishConfig` and bun lockfile (`#5273`).
- Renamed package to `@mem0/opencode-plugin` (`#5272`).
- Added plugin array to bundled `opencode.json` (`#5271`).
## 0.1.0 — Initial release
- **OpenCode plugin** (`@mem0/opencode-plugin` on npm): Pure TypeScript plugin using the `mem0ai` TS SDK — no Python, no shell scripts. Hooks into all 6 OpenCode events (`chat.message`, `tool.execute.before`, `tool.execute.after`, `experimental.chat.system.transform`, `experimental.session.compacting`, `shell.env`). Features: session start memory loading, per-prompt semantic search, error pattern detection with memory lookup, resume/remember intent detection, auto-capture every 3rd message, periodic save nudges, full metadata defaults injection (confidence, source, type, session_id, files, branch), identity injection for search/get/delete filters, type-filtered error pre-fetch (anti_pattern + bug_fix), pre-compaction memory capture, MEMORY.md write blocking, and secret redaction.
- **16 OpenCode-native skills** bundled in `opencode-skills/`: `context-loader`, `dream`, `export`, `forget`, `health`, `import`, `list-projects`, `mem0` (SDK reference), `memory-reviewer`, `onboard`, `peek`, `pin`, `remember`, `stats`, `switch-project`, `tour`. All skills are pure MCP-tool-based — no Python scripts, no shell scripts, no Claude Code dependencies.
- **Auto-install skills and commands (`installSkills()`):** On plugin load, copies all 16 skills to `.opencode/skills/` and creates command wrapper files in `.opencode/commands/` so they appear in the OpenCode `/` palette.
- **CLI installer (`cli.ts`):** `bunx @mem0/opencode-plugin install` auto-configures plugin and MCP server in `~/.config/opencode/opencode.json`.
+87 -6
View File
@@ -66,6 +66,38 @@ function redact(text: string): string {
return out;
}
function formatAge(createdAt: string): string {
try {
const dt = new Date(createdAt);
const now = Date.now();
const seconds = Math.floor((now - dt.getTime()) / 1000);
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
const days = Math.floor(seconds / 86400);
if (days === 1) return "1d ago";
if (days < 30) return `${days}d ago`;
return `${Math.floor(days / 30)}mo ago`;
} catch {
return "";
}
}
const TYPE_ICONS: Record<string, string> = {
decision: "⚖️",
anti_pattern: "🔴",
bug_fix: "🔴",
convention: "🔄",
task_learning: "🔵",
user_preference: "🟣",
session_summary: "📋",
session_state: "📋",
project_profile: "📖",
compact_summary: "📋",
auto_capture: "✅",
};
const FILE_READ_GATE_MIN_BYTES = 1500;
function loadGlobalSearch(): boolean {
try {
const settingsPath = join(homedir(), ".mem0", "settings.json");
@@ -308,9 +340,16 @@ const Mem0Plugin: Plugin = async (ctx) => {
const memories = extractMemories(res);
if (memories.length > 0) {
const memLines = memories
.map((m) => `- ${m.memory}`)
.map((m) => {
const meta = (m as any).metadata ?? {};
const cat = meta.type ?? "unknown";
const icon = TYPE_ICONS[cat] ?? "❓";
const age = (m as any).created_at ? formatAge((m as any).created_at) : "";
const ageStr = age ? ` (${age})` : "";
return `- ${icon} [${cat}]${ageStr} ${m.memory.slice(0, 120)}`;
})
.join("\n");
systemContext.push(`Prior context from mem0:\n${memLines}`);
systemContext.push(`### Recent Activity\n\n${memLines}`);
}
} catch {}
}
@@ -445,6 +484,41 @@ const Mem0Plugin: Plugin = async (ctx) => {
"tool.execute.before": async (input: any, output: any) => {
const toolName: string = input?.tool ?? "";
// File-context injection: before reading a file, search mem0 for prior work on it
if (toolName === "read" || toolName === "Read") {
const filePath = String(output?.args?.file_path ?? output?.args?.filePath ?? "");
if (filePath && filePath.length > 0) {
try {
const absPath = filePath.startsWith("/") ? filePath : resolve(process.cwd(), filePath);
const { statSync } = await import("fs");
const stat = statSync(absPath);
if (stat.isFile() && stat.size >= FILE_READ_GATE_MIN_BYTES) {
const searchFilters = globalSearch
? { OR: [{ user_id: "*" }] }
: { AND: [{ user_id: userId }, { app_id: appId }] };
const relPath = filePath.startsWith("/")
? filePath.replace(process.cwd() + "/", "")
: filePath;
const res = await mem0.search(relPath, {
filters: searchFilters,
topK: 5,
});
stats.searches++;
const memories = extractMemories(res);
if (memories.length > 0) {
const lines = memories.map((m) => {
const text = m.memory.slice(0, 150).replace(/\n/g, " ");
return `- ${text} [mem0:${m.id.slice(0, 8)}]`;
});
systemContext.push(
`Prior work on \`${relPath}\`:\n${lines.join("\n")}`,
);
}
}
} catch {}
}
}
if (WRITE_TOOLS.has(toolName)) {
const fp = String(
output?.args?.file_path ?? output?.args?.filePath ?? "",
@@ -611,15 +685,22 @@ const Mem0Plugin: Plugin = async (ctx) => {
) => {
try {
const compactSessionId = input?.sessionID ?? sessionId;
const summaryContent = `Session compacting. Project: ${appId}. Branch: ${branch}. Session: ${compactSessionId}. Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`;
// Session summary capture: store a structured summary of the session
const summaryPrompt = [
`Session summary for project ${appId} (branch: ${branch}).`,
`Session: ${compactSessionId}.`,
`Stats: ${stats.adds} memories stored, ${stats.searches} searches, ${stats.messages} messages.`,
`Extract and remember: what was requested, what was investigated, key decisions made, what was completed, and what needs to happen next.`,
].join(" ");
Promise.resolve().then(async () => {
try {
await mem0.add([{ role: "user", content: summaryContent }], {
await mem0.add([{ role: "user", content: summaryPrompt }], {
user_id: userId,
app_id: appId,
metadata: {
type: "session_state",
source: "pre-compaction",
type: "session_summary",
source: "opencode-stop",
session_id: compactSessionId,
branch,
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@mem0/opencode-plugin",
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
"main": "dist/index.js",