omo-memory 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ npm link
|
|
|
56
56
|
omo-memory init
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
Install and update run a local Codex cleanup migration through `postinstall`. It removes stale `omo-memory@islee23520` hook state and legacy hook files from `~/.codex` when they exist; it does not touch unrelated OMO/LazyCodex hooks.
|
|
60
|
+
|
|
59
61
|
## MCP registration
|
|
60
62
|
|
|
61
63
|
Register the same MCP server in every host that should read/write the current project's memory DB.
|
|
@@ -102,8 +104,7 @@ The response contains a new `sessionId` and project metadata only. It deliberate
|
|
|
102
104
|
}
|
|
103
105
|
```
|
|
104
106
|
|
|
105
|
-
This is local routing, not transcript scraping. OMO Memory does not automatically read full Codex or Grok transcripts.
|
|
106
|
-
The packaged `scripts/omo-memory-user-prompt.mjs` helper is the supported UserPromptSubmit hook target for adapters that can invoke a command with the hook payload on stdin. It records only the current user prompt as a redacted `user_prompt` event, ignores assistant output, and exits successfully without blocking the host when OMO Memory is unavailable.
|
|
107
|
+
This is local routing, not transcript scraping. OMO Memory does not automatically read full Codex or Grok transcripts. Adapters should record concise user actions, decisions, QA evidence, and handoffs through the CLI or MCP tools; they should retrieve memory only when the user explicitly asks for OMO Memory or when the current user input can be matched to recorded intent.
|
|
107
108
|
|
|
108
109
|
Use explicit retrieval for memory reads:
|
|
109
110
|
|
|
@@ -135,7 +136,7 @@ Initial stdio MCP tools:
|
|
|
135
136
|
|
|
136
137
|
## Updates
|
|
137
138
|
|
|
138
|
-
Installed CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup does not run the updater, so stdio handshakes stay clean.
|
|
139
|
+
Installed CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup does not run the updater, so stdio handshakes stay clean. The package `postinstall` cleanup also runs during these updates and removes legacy Codex `omo-memory@islee23520` hook registrations.
|
|
139
140
|
|
|
140
141
|
Manual update:
|
|
141
142
|
|
|
@@ -105,7 +105,7 @@ The tool returns:
|
|
|
105
105
|
- `sessionId`: the new session row for subsequent event/handoff writes.
|
|
106
106
|
- `project`: the current git/project namespace.
|
|
107
107
|
|
|
108
|
-
During the session,
|
|
108
|
+
During the session, adapters should write concise user-action summaries, task state, decisions, and evidence with the returned `sessionId`:
|
|
109
109
|
|
|
110
110
|
```json
|
|
111
111
|
{
|
|
@@ -125,7 +125,7 @@ Retrieval is opt-in or intent-gated:
|
|
|
125
125
|
- Use `memory_recent_events` only when the user explicitly asks for recent OMO Memory context.
|
|
126
126
|
- Use `memory_recall_events` when the current user input has a concrete query that can be matched to recorded summaries, decisions, or evidence.
|
|
127
127
|
- Do not automatically attach the last session to every user prompt.
|
|
128
|
-
-
|
|
128
|
+
- OMO Memory no longer installs Codex hooks. New package installs and updates run a `postinstall` cleanup that removes stale `omo-memory@islee23520` hook state and legacy hook files from `~/.codex`.
|
|
129
129
|
|
|
130
130
|
Use these tools:
|
|
131
131
|
|
|
@@ -144,7 +144,7 @@ Use these tools:
|
|
|
144
144
|
- `memory_global_list`
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
CLI updates: normal CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup intentionally does not auto-update because stdout/stderr must remain reserved for the protocol. Hosts that need pinned installs should set `OMO_MEMORY_AUTO_UPDATE=0` in the CLI environment.
|
|
147
|
+
CLI updates: normal CLI commands automatically launch a quiet background `npm install -g omo-memory@latest` at most once per day. MCP startup intentionally does not auto-update because stdout/stderr must remain reserved for the protocol. Hosts that need pinned installs should set `OMO_MEMORY_AUTO_UPDATE=0` in the CLI environment. Package install/update also runs the Codex stale-hook cleanup migration through `postinstall`.
|
|
148
148
|
|
|
149
149
|
Global migration is copy/import only. Adapters may scan for existing project-local `.omo/memory/state.sqlite` databases and import their event/session/handoff ledgers into a user-selected global SQLite file, but they must preserve source DBs and retain source provenance in the global store.
|
|
150
150
|
|
|
@@ -201,4 +201,4 @@ Example QA evidence:
|
|
|
201
201
|
|
|
202
202
|
OMO Memory's chronological ledger is authoritative: sessions, events, and handoffs record what happened in a project. Adapters should write concise summaries and evidence references to that ledger, then use explicit `recent`, `recall`, `handoff`, `export`, `purge`, and global event import/list operations.
|
|
203
203
|
|
|
204
|
-
OMO Memory does not provide automatic ontology extraction, graph visualization, retention scoring, durable-memory promotion, or OpenTUI workflows. Do not build adapter behavior that depends on those unavailable surfaces.
|
|
204
|
+
OMO Memory does not provide automatic ontology extraction, graph visualization, retention scoring, durable-memory promotion, or OpenTUI workflows. Do not build adapter behavior that depends on those unavailable surfaces.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omo-memory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Host-neutral local SQLite memory and session ledger for OMO adapters, exposed through CLI and MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"docs",
|
|
9
|
-
"scripts/
|
|
9
|
+
"scripts/postinstall-cleanup.mjs",
|
|
10
10
|
"README.md"
|
|
11
11
|
],
|
|
12
12
|
"bin": {
|
|
@@ -31,13 +31,14 @@
|
|
|
31
31
|
"format": "biome format --write .",
|
|
32
32
|
"check": "npm run lint && npm run typecheck && npm run smoke",
|
|
33
33
|
"start": "node dist/cli.js",
|
|
34
|
+
"postinstall": "node scripts/postinstall-cleanup.mjs",
|
|
34
35
|
"prepack": "npm run build",
|
|
35
36
|
"presmoke": "npm run build",
|
|
36
|
-
"smoke": "npm run smoke:cli && npm run smoke:mcp && npm run smoke:
|
|
37
|
+
"smoke": "npm run smoke:cli && npm run smoke:mcp && npm run smoke:legacy && npm run smoke:codex-hook-migration",
|
|
37
38
|
"smoke:cli": "node scripts/smoke-cli.mjs",
|
|
38
39
|
"smoke:mcp": "node scripts/smoke-mcp.mjs",
|
|
39
|
-
"smoke:hook": "node scripts/verify-user-prompt-hook.mjs",
|
|
40
40
|
"smoke:legacy": "node scripts/verify-legacy-schema-compat.mjs",
|
|
41
|
+
"smoke:codex-hook-migration": "node scripts/verify-codex-hook-migration.mjs",
|
|
41
42
|
"mcp": "node dist/cli.js mcp",
|
|
42
43
|
"issue:epic": "node scripts/create-epic-issue.mjs"
|
|
43
44
|
},
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const CODEX_ROOT = join(homedir(), ".codex");
|
|
7
|
+
const LEGACY_PLUGIN_ROOT = join(CODEX_ROOT, "local-marketplaces", "islee23520", "plugins", "omo-memory");
|
|
8
|
+
const LEGACY_HOOK_STATE_PREFIX = '[hooks.state."omo-memory@islee23520:';
|
|
9
|
+
const LEGACY_HOOK_ARTIFACTS = [
|
|
10
|
+
join(LEGACY_PLUGIN_ROOT, "hooks", "hooks.json"),
|
|
11
|
+
join(LEGACY_PLUGIN_ROOT, "scripts", "omo-memory-user-prompt.mjs"),
|
|
12
|
+
join(LEGACY_PLUGIN_ROOT, "scripts", "omo-memory-session.mjs"),
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function cleanupCodexHooks() {
|
|
16
|
+
const changed = [];
|
|
17
|
+
const configPath = join(CODEX_ROOT, "config.toml");
|
|
18
|
+
if (rewriteCodexConfig(configPath)) changed.push(configPath);
|
|
19
|
+
if (rewritePluginManifest(join(LEGACY_PLUGIN_ROOT, ".codex-plugin", "plugin.json"))) {
|
|
20
|
+
changed.push(join(LEGACY_PLUGIN_ROOT, ".codex-plugin", "plugin.json"));
|
|
21
|
+
}
|
|
22
|
+
for (const artifact of LEGACY_HOOK_ARTIFACTS) {
|
|
23
|
+
if (!existsSync(artifact)) continue;
|
|
24
|
+
rmSync(artifact, { force: true });
|
|
25
|
+
changed.push(artifact);
|
|
26
|
+
}
|
|
27
|
+
return changed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function rewriteCodexConfig(configPath) {
|
|
31
|
+
if (!existsSync(configPath)) return false;
|
|
32
|
+
const before = readFileSync(configPath, "utf8");
|
|
33
|
+
const after = removeTomlTables(before, (header) => header.startsWith(LEGACY_HOOK_STATE_PREFIX));
|
|
34
|
+
if (after === before) return false;
|
|
35
|
+
writeFileSync(configPath, after);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function rewritePluginManifest(manifestPath) {
|
|
40
|
+
if (!existsSync(manifestPath)) return false;
|
|
41
|
+
const raw = readFileSync(manifestPath, "utf8");
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (!isObject(parsed)) return false;
|
|
44
|
+
const next = removeHookManifestFields(parsed);
|
|
45
|
+
const after = `${JSON.stringify(next, null, 2)}\n`;
|
|
46
|
+
if (after === raw) return false;
|
|
47
|
+
writeFileSync(manifestPath, after);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function removeHookManifestFields(manifest) {
|
|
52
|
+
const next = { ...manifest };
|
|
53
|
+
delete next.hooks;
|
|
54
|
+
next.keywords = removeStringFromArray(next.keywords, "hooks");
|
|
55
|
+
if (isObject(next.interface)) {
|
|
56
|
+
next.interface = {
|
|
57
|
+
...next.interface,
|
|
58
|
+
capabilities: removeStringFromArray(next.interface.capabilities, "Hooks"),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return next;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function removeStringFromArray(value, removedValue) {
|
|
65
|
+
if (!Array.isArray(value)) return value;
|
|
66
|
+
return value.filter((item) => item !== removedValue);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function removeTomlTables(input, shouldRemoveHeader) {
|
|
70
|
+
const lines = input.split(/\r?\n/);
|
|
71
|
+
const output = [];
|
|
72
|
+
let removing = false;
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
if (/^\[[^\]]+/.test(line)) removing = shouldRemoveHeader(line.trim());
|
|
75
|
+
if (!removing) output.push(line);
|
|
76
|
+
}
|
|
77
|
+
return output.join("\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isObject(value) {
|
|
81
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const changed = cleanupCodexHooks();
|
|
86
|
+
process.stdout.write(`${JSON.stringify({ ok: true, migration: "codex-omo-memory-hooks-removed", changed }, null, 2)}\n`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
process.stdout.write(`${JSON.stringify({ ok: false, migration: "codex-omo-memory-hooks-removed", error: message }, null, 2)}\n`);
|
|
90
|
+
process.exitCode = 0;
|
|
91
|
+
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
const MAX_SUMMARY_CHARS = 4000;
|
|
6
|
-
|
|
7
|
-
const host = process.env["OMO_MEMORY_HOST"] ?? "unknown";
|
|
8
|
-
const adapter = process.env["OMO_MEMORY_ADAPTER"] ?? "unknown";
|
|
9
|
-
|
|
10
|
-
const input = readStdin();
|
|
11
|
-
const payload = parseJson(input);
|
|
12
|
-
const prompt = extractPrompt(payload);
|
|
13
|
-
|
|
14
|
-
if (prompt === null) process.exit(0);
|
|
15
|
-
|
|
16
|
-
const summary = truncate(prompt.replace(/\s+/g, " ").trim(), MAX_SUMMARY_CHARS);
|
|
17
|
-
if (summary.length === 0) process.exit(0);
|
|
18
|
-
|
|
19
|
-
const hookSessionId = readString(payload, "sessionId") ?? readString(payload, "session_id");
|
|
20
|
-
const workspaceRoot = readString(payload, "workspaceRoot") ?? readString(payload, "cwd");
|
|
21
|
-
const metadata = {
|
|
22
|
-
source: "hook",
|
|
23
|
-
hookEventName: readString(payload, "hookEventName") ?? process.env["GROK_HOOK_EVENT"] ?? process.env["CODEX_HOOK_EVENT"] ?? "UserPromptSubmit",
|
|
24
|
-
host,
|
|
25
|
-
adapter,
|
|
26
|
-
...(hookSessionId === null ? {} : { hookSessionId }),
|
|
27
|
-
...(workspaceRoot === null ? {} : { workspaceRoot }),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const args = ["event", "record", "--type", "user_prompt", "--summary", summary, "--payload-json", JSON.stringify(metadata)];
|
|
31
|
-
|
|
32
|
-
const result = runOmoMemory(args) ?? runNpx(args);
|
|
33
|
-
if (result === undefined || result.status !== 0) process.exit(0);
|
|
34
|
-
|
|
35
|
-
function readStdin() {
|
|
36
|
-
try {
|
|
37
|
-
return readFileSync(0, "utf8");
|
|
38
|
-
} catch (error) {
|
|
39
|
-
if (error instanceof Error) process.exit(0);
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function parseJson(raw) {
|
|
45
|
-
if (raw.trim().length === 0) return {};
|
|
46
|
-
try {
|
|
47
|
-
const parsed = JSON.parse(raw);
|
|
48
|
-
return isRecord(parsed) ? parsed : {};
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (error instanceof SyntaxError) return {};
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function extractPrompt(value) {
|
|
56
|
-
if (!isRecord(value)) return null;
|
|
57
|
-
for (const key of ["prompt", "userPrompt", "user_prompt", "message", "text", "input"]) {
|
|
58
|
-
const direct = readString(value, key);
|
|
59
|
-
if (direct !== null) return direct;
|
|
60
|
-
}
|
|
61
|
-
const nestedPrompt =
|
|
62
|
-
readNestedString(value, ["toolInput", "prompt"]) ?? readNestedString(value, ["payload", "prompt"]) ?? readNestedString(value, ["data", "prompt"]);
|
|
63
|
-
if (nestedPrompt !== null) return nestedPrompt;
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function readNestedString(value, path) {
|
|
68
|
-
let cursor = value;
|
|
69
|
-
for (const key of path) {
|
|
70
|
-
if (!isRecord(cursor)) return null;
|
|
71
|
-
cursor = cursor[key];
|
|
72
|
-
}
|
|
73
|
-
return typeof cursor === "string" ? cursor : null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function readString(value, key) {
|
|
77
|
-
const item = value[key];
|
|
78
|
-
return typeof item === "string" ? item : null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function truncate(value, maxLength) {
|
|
82
|
-
if (value.length <= maxLength) return value;
|
|
83
|
-
return `${value.slice(0, maxLength - 15)} [TRUNCATED]`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function runOmoMemory(args) {
|
|
87
|
-
const command = process.env["OMO_MEMORY_CLI"] ?? "omo-memory";
|
|
88
|
-
return run(command, args);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function runNpx(args) {
|
|
92
|
-
return run("npx", ["-y", "omo-memory", ...args]);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function run(command, args) {
|
|
96
|
-
const result = spawnSync(command, args, {
|
|
97
|
-
encoding: "utf8",
|
|
98
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
99
|
-
timeout: 5000,
|
|
100
|
-
});
|
|
101
|
-
if (result.error?.code === "ENOENT") return undefined;
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function isRecord(value) {
|
|
106
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
107
|
-
}
|