UNPKG

open-memex

Version:

Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.

105 lines (96 loc) • 3.58 kB
import type { z } from "zod"; import type { ToolContext, ToolResult } from "@opencode-ai/plugin"; import type { Scope } from "../scope.ts"; import { PERSONAL_SCOPE } from "../scope.ts"; import type { MyOMemoryConfig } from "../config.ts"; import { syncScope } from "../store/sync.ts"; import { addMemory, searchMemories, listMemories, supersedeMemory, forgetMemory, memoryAddArgs, memorySearchArgs, memoryListArgs, memorySupersedeArgs, memoryForgetArgs, TOOL_DESCRIPTIONS, withOutboxNote, } from "./ops.ts"; // Local stand-in for the SDK's tool() helper — same signature, and the SDK // function is a runtime identity (`return input`). Do NOT import it from // @opencode-ai/plugin at runtime: that package is a devDependency, and when // opencode loads this plugin by file:// URL from a global npm install // nothing resolves the module — the import fails and opencode silently // skips the whole plugin: no error, no tools (D58). function tool<Args extends z.ZodRawShape>(input: { description: string; args: Args; execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>; }): { description: string; args: Args; execute: typeof input.execute } { return input; } export function makeTools(getScope: () => Scope, cfg: MyOMemoryConfig) { // D64: the plugin host syncs only at bootstrap, so without this the // index goes stale for the whole session — memories added through the // CLI or another client stay invisible to the agent, and same-content // duplicates accumulate. Mirror the MCP server: reconcile from disk // before every tool call. syncScope is mtime-based, so the steady-state // cost is one directory scan. const syncFirst = () => { try { syncScope(getScope().key, "request"); syncScope(PERSONAL_SCOPE.key, "request"); } catch (err) { // Deliberate asymmetry with the MCP adapter (which lets sync errors // propagate as tool errors): a failed sync must not take the plugin // tools down — the index may be stale for this call but the tool // still answers. Loud at debug, silent otherwise. if (cfg.logLevel === "debug") { console.error("[open-memex] pre-tool sync failed:", err); } } }; const memory_add = tool({ description: TOOL_DESCRIPTIONS.memory_add, args: memoryAddArgs, async execute(args) { syncFirst(); return withOutboxNote(getScope().key, addMemory(getScope, cfg, args)); }, }); const memory_search = tool({ description: TOOL_DESCRIPTIONS.memory_search, args: memorySearchArgs, async execute(args) { syncFirst(); return searchMemories(getScope, args); }, }); const memory_list = tool({ description: TOOL_DESCRIPTIONS.memory_list, args: memoryListArgs, async execute(args) { syncFirst(); return listMemories(getScope, args); }, }); const memory_supersede = tool({ description: TOOL_DESCRIPTIONS.memory_supersede, args: memorySupersedeArgs, async execute(args) { syncFirst(); return withOutboxNote(getScope().key, supersedeMemory(cfg, args)); }, }); const memory_forget = tool({ description: TOOL_DESCRIPTIONS.memory_forget, args: memoryForgetArgs, async execute(args) { syncFirst(); return withOutboxNote(getScope().key, forgetMemory(args)); }, }); return { memory_add, memory_search, memory_list, memory_forget, memory_supersede }; }