memoryrouter-codex 1.6.1 → 1.8.0

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
@@ -2,29 +2,39 @@
2
2
 
3
3
  Native Codex integration for durable MemoryRouter memory. It uses Codex's current MCP, lifecycle hooks, and `AGENTS.md` surfaces rather than pretending that model-selected MCP calls alone are automatic memory.
4
4
 
5
- > **Release status:** `memoryrouter-codex@1.1.0` is the published npm release. Its automatic capture is message-only: one user request plus the final assistant response, with no tool payloads.
5
+ > **Release status:** `memoryrouter-codex` is the published npm release. Its automatic capture is message-only: one user request plus the final assistant response, with no tool payloads.
6
6
 
7
7
  ## What this integration does
8
8
 
9
+ Every turn, automatically: **recall before the prompt, save after the turn.** What is saved is exactly two things per turn, the user's message and the assistant's final response. Tool calls, tool output, reasoning, plans, intermediate assistant messages and subagent output are never saved.
10
+
9
11
  | Layer | Behavior |
10
12
  | --- | --- |
11
- | `UserPromptSubmit` hook | Searches MemoryRouter before every prompt and injects bounded, project-scoped context as developer context. |
12
- | `Stop` hook | Captures only the user request and final assistant response after each completed turn. Tool names, inputs, and results are excluded. |
13
- | `SubagentStop` hook | Captures the stable final outcome of completed subagents without parsing their unstable transcript files. |
14
- | `PreCompact` / compact `SessionStart` | Checkpoints only the current user request and final assistant response when present, then recalls context before Codex continues after compaction. |
15
- | `SessionEnd` | Best-effort message-only final checkpoint within Codex's three-second SessionEnd budget. |
16
- | Remote MCP | OAuth-based explicit search/store through `https://mcp.memoryrouter.ai/mcp`. |
17
- | Local MCP fallback | API-key-authenticated stdio tools with strict project filtering, handles, local suppression, status, and full-vault clear confirmation. |
13
+ | `UserPromptSubmit` hook | Searches your whole MemoryRouter vault with the prompt and injects what it finds as developer context for that turn. The vault is shared with Claude Code and every other MemoryRouter surface. Records the prompt for this turn. |
14
+ | `Stop` hook | Saves the user's message and the final assistant response for the turn. Nothing else. |
15
+ | `SessionStart` hook | Restores context after Codex compacts a long chat (key mode also on new/resumed chats). |
16
+ | `SessionEnd` (key mode) | Only if the last turn never finished (interrupted), saves that turn's user message. A completed turn is never saved twice. |
17
+ | Remote MCP | OAuth-based explicit search/store through `https://mcp.memoryrouter.ai/mcp` (`search_memories`, `store_memory`, ...). |
18
+ | Local MCP fallback | API-key-authenticated stdio tools over the same whole vault. |
18
19
  | `AGENTS.md` | Tells Codex when to recall/store and how MemoryRouter coexists with native Memories. |
19
20
 
20
- MemoryRouter is the **cross-machine, cross-tool, team-shareable, and auditable** layer. Codex's native Memories remain useful as an automated local personal recall layer.
21
+ **Two ways the hooks reach MemoryRouter (1.8.0):**
22
+
23
+ - **Over your MemoryRouter sign-in (no API key).** Default when you signed in with OAuth (Codex app Settings > Plugins > Authenticate, or `codex mcp login memoryrouter`) and no key is configured. The hooks are Codex `mcp_tool` hooks that call hidden lifecycle tools on the already-connected `memoryrouter` server (`codex_turn_start`, `codex_turn_end`, `codex_session_start`) with your OAuth token and the vault you picked when you signed in. Nothing secret is written to disk.
24
+ - **Local runtime with a Memory Key.** Used when a key is configured (`MEMORYROUTER_API_KEY`, `--api-key-stdin`, or `memoryrouter-claude`'s `~/.memoryrouter/config.json`). Same per-turn behavior.
25
+
26
+ Force either with `--hooks-via mcp` or `--hooks-via command`.
27
+
28
+ 1.8.0 removed the `SubagentStop` and `PreCompact` capture hooks: subagent output is intermediate work, and the PreCompact checkpoint re-saved a prompt the `Stop` hook already captured.
29
+
30
+ MemoryRouter is the **cross-machine, cross-tool, team-shareable, and auditable** layer: one memory across every session, repo and tool, shared by Codex, Claude Code, ChatGPT, and Claude. Codex's native Memories remain useful as an automated local personal recall layer.
21
31
 
22
32
  ## Requirements and supported surfaces
23
33
 
24
34
  - Node.js 18 or newer.
25
35
  - **Codex 0.145.0 or newer** for the complete lifecycle-hook integration. Version 0.146.0 was tested directly.
26
36
  - Local Codex CLI, ChatGPT desktop app's local Codex host, and Codex IDE extension share `config.toml` MCP configuration.
27
- - User/project hooks require the Codex host to support and trust command hooks. Use `/hooks` to review them.
37
+ - Hooks must be trusted once: Codex app **Settings > Hooks**, or `/hooks` in the CLI. OAuth (`mcp_tool`) hooks need a Codex build with MCP tool hooks (the current app and CLI 0.166 have them).
28
38
  - Project `.codex` config is loaded only for trusted projects.
29
39
  - ChatGPT web does **not** read a laptop's `~/.codex/config.toml` or local hooks. Use MemoryRouter's hosted plugin/connector surface there.
30
40
  - Codex cloud environments do not automatically inherit host-local user config or secrets. A project-scope install can travel with a repository, but the environment still needs Node, project trust, package/network availability for the stdio fallback, and `MEMORYROUTER_API_KEY` for hooks.
@@ -33,19 +43,40 @@ There is no dependency on the obsolete `experimental_use_rmcp_client` setting in
33
43
 
34
44
  ## Install
35
45
 
36
- ### Recommended: remote OAuth MCP plus automatic hooks
46
+ ### Codex app (Mac): two steps, no key
47
+
48
+ 1. Add the server in the app: **Settings > Plugins > Add > Add MCP server**, Type **Streamable HTTP**, Name `memoryrouter`, URL `https://mcp.memoryrouter.ai/mcp`, **Save**, then **Authenticate** and sign in (or create an account) in the browser tab that opens.
49
+ 2. In a Codex chat, paste:
50
+
51
+ > Run `npx -y memoryrouter-codex@latest init` with network access, then tell me to trust the MemoryRouter hooks in Settings > Hooks.
52
+
53
+ Then trust the three MemoryRouter hooks in **Settings > Hooks** and open a **New chat**.
54
+
55
+ `init` keeps the server the app added (and its sign-in), adds the three `mcp_tool` hooks to `~/.codex/hooks.json` and the MemoryRouter guidance to `~/.codex/AGENTS.md`. The Codex CLI and IDE extension on the same Mac share all of it.
56
+
57
+ ### Codex CLI, OAuth (no key)
37
58
 
38
- Obtain a MemoryRouter memory key from your account. Pass it on stdin so it does not appear in shell history:
59
+ ```bash
60
+ npx -y memoryrouter-codex init --scope user
61
+ codex mcp login memoryrouter
62
+ npx -y memoryrouter-codex doctor --scope user
63
+ ```
64
+
65
+ Then start Codex, trust the MemoryRouter hooks in `/hooks`, and check `/mcp verbose`.
66
+
67
+ ### With a Memory Key (local hook runtime)
68
+
69
+ Pass the key on stdin so it does not appear in shell history:
39
70
 
40
71
  ```bash
41
72
  printf '%s' "$MEMORYROUTER_API_KEY" | \
42
- npx -y memoryrouter-codex@1.1.0 init --scope user --auth oauth --api-key-stdin
73
+ npx -y memoryrouter-codex init --scope user --auth oauth --api-key-stdin
43
74
 
44
75
  codex mcp login memoryrouter
45
76
  memoryrouter-codex doctor --scope user
46
77
  ```
47
78
 
48
- Then start Codex and inspect:
79
+ Then start Codex and inspect (trust the MemoryRouter hook definitions in `/hooks`):
49
80
 
50
81
  ```text
51
82
  /mcp verbose
@@ -54,14 +85,16 @@ Then start Codex and inspect:
54
85
 
55
86
  The API key is written to `~/.memoryrouter/codex/config.json` with mode `0600`; the directory is `0700`. It is never written to `config.toml`, `hooks.json`, `AGENTS.md`, or a repository.
56
87
 
57
- If you prefer not to persist the key, omit `--api-key-stdin` and ensure `MEMORYROUTER_API_KEY` is in the environment of the Codex host. Without either source, remote OAuth MCP tools can work after login, but automatic hooks intentionally become no-op/fail-open.
88
+ Already using MemoryRouter in Claude Code? When no Codex key is configured, the hooks reuse the `memoryrouter-claude` key in `~/.memoryrouter/config.json`, so both agents use the same vault (one memory for every session, repo and tool) with no second paste. Key precedence: `MEMORYROUTER_API_KEY`, then the Codex private config, then the Claude Code config.
89
+
90
+ If you prefer not to persist the key, omit `--api-key-stdin` and ensure `MEMORYROUTER_API_KEY` is in the environment of the Codex host. Without any key, `init` installs the OAuth (`mcp_tool`) hooks instead, which work as soon as you are signed in.
58
91
 
59
92
  ### Project scope
60
93
 
61
94
  ```bash
62
95
  cd /path/to/repository
63
96
  printf '%s' "$MEMORYROUTER_API_KEY" | \
64
- npx -y memoryrouter-codex@1.1.0 init --scope project --auth oauth --api-key-stdin
97
+ npx -y memoryrouter-codex init --scope project --auth oauth --api-key-stdin
65
98
  codex mcp login memoryrouter
66
99
  ```
67
100
 
@@ -71,7 +104,7 @@ This writes managed blocks/files under the repository's `AGENTS.md` and `.codex/
71
104
 
72
105
  ```bash
73
106
  printf '%s' "$MEMORYROUTER_API_KEY" | \
74
- npx -y memoryrouter-codex@1.1.0 init --scope user --auth api-key --api-key-stdin
107
+ npx -y memoryrouter-codex init --scope user --auth api-key --api-key-stdin
75
108
  ```
76
109
 
77
110
  This uses a local stdio MCP server and the MemoryRouter API. It does not put the memory key in Codex config. The fallback tools are:
@@ -110,25 +143,27 @@ The installer preserves unrelated TOML byte-for-byte outside its managed block.
110
143
 
111
144
  Hooks are merged into `hooks.json`; foreign hooks remain. Re-running `init` upgrades/replaces only MemoryRouter-owned handlers. `uninstall` removes only owned handlers and managed blocks.
112
145
 
113
- ## Project identity and scoping
146
+ ## One vault per user (shared with Claude Code and every MemoryRouter surface)
147
+
148
+ Since **1.7.0**, every hook call (recall before each prompt, capture after each turn, compaction checkpoints, post-compaction recall) and every local MCP tool call reads and writes your **whole vault**. No `session_id` is sent, so there is no per-session or per-repository partition. That gives you:
114
149
 
115
- The runtime computes:
150
+ - **A new Codex session recalls earlier sessions**, in this repository and every other one.
151
+ - **Codex and Claude Code share one memory.** A decision captured in Claude Code in repo A is recalled by Codex in repo B, and the other way round (requires `memoryrouter-claude` 2.7.0 or newer on the same vault).
152
+ - **Everything else on the vault is recalled too:** saves from the hosted MCP (`store_memory` from ChatGPT, Claude, or `codex mcp login`), consolidated reflections, the server's pending buffer, and Codex envelopes from any release. Only handles you suppressed locally with `memory_forget` are hidden.
153
+
154
+ **Repository tag.** Codex still computes the same `ccp_` id as Claude Code:
116
155
 
117
156
  ```text
118
- proj_<first 20 hex chars of SHA-256(canonical project identity)>
157
+ ccp_<first 24 hex chars of SHA-256(canonical project identity)>
119
158
  ```
120
159
 
121
- Identity preference:
122
-
123
- 1. Canonical `origin` git URL, with credentials removed and `.git` normalized.
124
- 2. Git common directory (so local worktrees share a project).
125
- 3. Absolute path fallback.
160
+ from, in order, an explicit id (`MEMORYROUTER_PROJECT_ID` or `projectId` in `.memoryrouter.json`), the normalized git remote, the git common directory, then the directory path. It is metadata only: Codex stamps it on the turns it saves and labels recalled entries from the current repository as `this repo`. It never limits what is recalled. Only the opaque hash leaves the machine.
126
161
 
127
- Only the opaque handle is stored remotely. Local paths are not sent as identity metadata. A canonical remote makes identity stable across worktrees and machines.
162
+ **One user, one vault.** When you also connect MemoryRouter over OAuth (`codex mcp login memoryrouter`, ChatGPT, Claude), pick the same vault your hook key belongs to. `init` asks `https://auth.memoryrouter.ai/v1/vault-binding` whether the hook key is the vault your OAuth connections selected: on a mismatch it stops before writing anything and names the selected vault; with no connection yet it tells you which vault to pick at login. `doctor` reports the same `one-vault` check. The response never contains a key.
128
163
 
129
- Automatic memories use an envelope containing project handle, stable memory handle, kind, and capture time. Retrieval requests extra semantic candidates and then **strictly filters** to the current project handle before context injection. Legacy/unscoped vault records remain available through explicit remote MCP search but are not automatically injected in project mode.
164
+ `.memoryrouterignore` (or `enabled: false` in `.memoryrouter.json`) still disables hooks for a repository. The pre-1.7 settings `retrievalScope`, `includeGlobal`, and `memoryScope` are ignored and removed from the private config.
130
165
 
131
- Set `retrievalScope` to `"vault"` in the private config only if you intentionally want unscoped automatic recall.
166
+ **Upgrading from 1.6.x.** Releases before 1.7.0 used a per-Codex-session partition (`mrcodex:<project>:<session>`), so every new session started empty and Codex never saw Claude Code's memory. When 1.7.0 shipped, those partitions (and `memoryrouter-claude`'s per-repository `ccp_` partitions) were moved into each vault's main memory server-side, with stored embeddings kept, exact repeats skipped, and unflushed buffers saved, so old memories are recalled again with nothing lost or duplicated. Re-run `init` to upgrade the installed runtime.
132
167
 
133
168
  ## Historical import (agent-driven)
134
169
 
@@ -222,15 +257,15 @@ Doctor checks Codex version, managed config, runtime version, hook count, `AGENT
222
257
  Upgrade idempotently by re-running the pinned/new package:
223
258
 
224
259
  ```bash
225
- npx -y memoryrouter-codex@1.1.0 init --scope user
260
+ npx -y memoryrouter-codex init --scope user
226
261
  ```
227
262
 
228
263
  Uninstall:
229
264
 
230
265
  ```bash
231
- npx -y memoryrouter-codex@1.1.0 uninstall --scope user
266
+ npx -y memoryrouter-codex uninstall --scope user
232
267
  # or
233
- npx -y memoryrouter-codex@1.1.0 uninstall --scope project --project-dir /repo
268
+ npx -y memoryrouter-codex uninstall --scope project --project-dir /repo
234
269
  ```
235
270
 
236
271
  Uninstall deliberately retains `~/.memoryrouter/codex/` because another project installation may use the credentials and local suppression state. Delete that private directory manually only when no integration uses it.
@@ -242,7 +277,7 @@ Uninstall deliberately retains `~/.memoryrouter/codex/` because another project
242
277
  - Tool-call records are intentionally excluded. MemoryRouter does not store tool names, tool IDs, tool inputs, tool results, commands, patches, or other `PostToolUse` payloads from automatic Codex capture. Important outcomes must appear in the final assistant response to be retained.
243
278
  - Some specialized tool paths can opt out of hooks. Codex documents hooks as a useful extension surface, not a complete enforcement boundary.
244
279
  - SessionEnd can happen after a 30-minute idle period and has a three-second maximum. It is a best-effort checkpoint; normal `Stop` capture is the primary write path.
245
- - Project scoping is strict for this package's automatic retrieval/local MCP. The existing hosted remote MCP server has no first-class project argument, so use local tools/hooks when cross-project isolation is required.
280
+ - Everything shares one vault per user. Strict isolation between two bodies of work requires a separate account and vault.
246
281
  - Per-memory server deletion is not available through the current API. Local suppression and explicit full-vault clear are accurately labeled.
247
282
  - API-level idempotency is not yet available; timeout-after-acceptance duplicates remain possible.
248
283
 
package/bin/cli.cjs CHANGED
@@ -23,7 +23,7 @@ function parseArgs(argv) {
23
23
  }
24
24
 
25
25
  function usage() {
26
- return `memoryrouter-codex ${INTEGRATION_VERSION}\n\nUsage:\n memoryrouter-codex init [--scope user|project] [--auth oauth|api-key]\n [--project-dir PATH] [--api-key-stdin]\n [--no-hooks] [--no-agents]\n memoryrouter-codex doctor [--scope user|project] [--project-dir PATH] [--offline]\n memoryrouter-codex uninstall [--scope user|project] [--project-dir PATH]\n memoryrouter-codex reflect [--tier 1|2] [--batches N|all] [--max-tokens N] [--dry-run]\n memoryrouter-codex import [--instructions [--run]] [--file PATH] [--dismiss] [--yes]\n memoryrouter-codex mcp-server\n\nImport:\n Import historical Codex conversations into your vault. --instructions\n fetches the server's extraction prompt for the host agent to execute\n (--run spawns it via codex exec); --file uploads a JSONL extract with a\n preview, price quote, explicit approval (--yes for non-interactive),\n idempotent batches, and resume; --dismiss silences the import nudge\n permanently. Re-running --file is always safe: committed batches replay\n for free and duplicates are never re-billed.\n\nReflect:\n Consolidates unconsolidated memories into reflections using Codex itself as\n the model (codex exec, you pay your own inference). Server-authored\n instructions; resumable; no local state. --dry-run prints the checkout and\n lets the lease expire without committing.\n Automatic mode: when the server reports consolidation debt above its\n threshold, the hooks spawn this same loop in the background (max 3 batches\n per trigger, 30 minute cooldown, machine-wide lockfile at\n ~/.memoryrouter/consolidate.lock). Opt out with "autoConsolidate": false in\n ~/.memoryrouter/codex/config.json. Logs: ~/.memoryrouter/consolidate.log\n\nAuth modes:\n oauth Current remote MCP at ${'https://mcp.memoryrouter.ai/mcp'} (default).\n Run \`codex mcp login memoryrouter\` after init. Hooks additionally\n require MEMORYROUTER_API_KEY or a key saved with --api-key-stdin.\n api-key Local stdio MCP fallback plus automatic hooks. The key is read only\n from the environment/private config, never from project files.\n\nSecure key setup:\n printf '%s' "$MEMORYROUTER_API_KEY" | memoryrouter-codex init --api-key-stdin\n`;
26
+ return `memoryrouter-codex ${INTEGRATION_VERSION}\n\nUsage:\n memoryrouter-codex init [--scope user|project] [--auth oauth|api-key]\n [--project-dir PATH] [--api-key-stdin]\n [--hooks-via mcp|command] [--no-hooks] [--no-agents]\n memoryrouter-codex doctor [--scope user|project] [--project-dir PATH] [--offline]\n memoryrouter-codex uninstall [--scope user|project] [--project-dir PATH]\n memoryrouter-codex reflect [--tier 1|2] [--batches N|all] [--max-tokens N] [--dry-run]\n memoryrouter-codex import [--instructions [--run]] [--file PATH] [--dismiss] [--yes]\n memoryrouter-codex mcp-server\n\nImport:\n Import historical Codex conversations into your vault. --instructions\n fetches the server's extraction prompt for the host agent to execute\n (--run spawns it via codex exec); --file uploads a JSONL extract with a\n preview, price quote, explicit approval (--yes for non-interactive),\n idempotent batches, and resume; --dismiss silences the import nudge\n permanently. Re-running --file is always safe: committed batches replay\n for free and duplicates are never re-billed.\n\nReflect:\n Consolidates unconsolidated memories into reflections using Codex itself as\n the model (codex exec, you pay your own inference). Server-authored\n instructions; resumable; no local state. --dry-run prints the checkout and\n lets the lease expire without committing.\n Automatic mode: when the server reports consolidation debt above its\n threshold, the hooks spawn this same loop in the background (max 3 batches\n per trigger, 30 minute cooldown, machine-wide lockfile at\n ~/.memoryrouter/consolidate.lock). Opt out with "autoConsolidate": false in\n ~/.memoryrouter/codex/config.json. Logs: ~/.memoryrouter/consolidate.log\n\nAuth modes:\n oauth Current remote MCP at ${'https://mcp.memoryrouter.ai/mcp'} (default).\n Sign in once (Codex app: Settings > Plugins > Authenticate; CLI:\n \`codex mcp login memoryrouter\`). With no API key, the automatic\n hooks call MemoryRouter over that same sign-in (Codex mcp_tool\n hooks): recall before every prompt, save the prompt and the final\n response after every turn. No key on disk. If a key is configured\n (MEMORYROUTER_API_KEY, --api-key-stdin, or memoryrouter-claude's\n ~/.memoryrouter/config.json) the local hook runtime is used\n instead; force either with --hooks-via mcp|command.\n A server you already added in the Codex app is kept as is.\n api-key Local stdio MCP fallback plus automatic hooks. The key is read only\n from the environment/private config, never from project files.\n\nSecure key setup:\n printf '%s' "$MEMORYROUTER_API_KEY" | memoryrouter-codex init --api-key-stdin\n`;
27
27
  }
28
28
 
29
29
  function optionsFromArgs(args) {
@@ -35,6 +35,7 @@ function optionsFromArgs(args) {
35
35
  agents: args.agents,
36
36
  force: Boolean(args.force),
37
37
  offline: Boolean(args.offline),
38
+ ...(args.hooksVia ? { hookMode: String(args.hooksVia) } : {}),
38
39
  };
39
40
  if (args.apiKeyStdin) {
40
41
  options.apiKey = fs.readFileSync(0, 'utf8').trim();
@@ -111,14 +112,40 @@ async function main() {
111
112
  }
112
113
  const options = optionsFromArgs(args);
113
114
  if (command === 'init') {
115
+ // One user = one vault: before writing anything, make sure the Memory Key
116
+ // the hooks will use is the vault selected in OAuth (codex mcp login).
117
+ const { loadConfig, getApiKey } = require('../runtime/config.cjs');
118
+ const { checkVaultBinding, vaultLabel, VaultMismatchError } = require('../runtime/vault-binding.cjs');
119
+ const hookKey = options.apiKey || getApiKey(process.env, loadConfig(process.env));
120
+ let binding = null;
121
+ if (hookKey && !options.offline) {
122
+ binding = await checkVaultBinding(hookKey);
123
+ if (binding.status === 'mismatch') throw new VaultMismatchError(binding);
124
+ }
114
125
  const result = init(options);
115
- process.stdout.write(`MemoryRouter Codex ${INTEGRATION_VERSION} installed (${result.layout.scope}, ${result.auth}).\n`);
116
- process.stdout.write(` config: ${result.layout.configFile}\n`);
126
+ process.stdout.write(`MemoryRouter Codex ${INTEGRATION_VERSION} installed (${result.layout.scope}, ${result.auth}${result.hookMode === 'mcp' ? ', hooks over your MemoryRouter sign-in' : ''}).\n`);
127
+ process.stdout.write(` config: ${result.layout.configFile}${result.adoptedServer ? ` (kept your existing "${result.adoptedServer}" server)` : ''}\n`);
117
128
  if (options.hooks !== false) process.stdout.write(` hooks: ${result.layout.hooksFile}\n`);
118
129
  if (options.agents !== false) process.stdout.write(` guide: ${result.layout.agentsFile}\n`);
119
- if (result.auth === 'oauth') process.stdout.write('\nNext: codex mcp login memoryrouter\nThen open Codex and run /mcp verbose and /hooks.\n');
120
- if (!result.automaticMemory) process.stdout.write('\nRemote MCP tools are configured, but automatic hooks are fail-open/no-op until MEMORYROUTER_API_KEY is available or init is re-run with --api-key-stdin.\n');
121
- process.stdout.write('\nRun: memoryrouter-codex doctor\n');
130
+ if (binding && binding.status === 'match') process.stdout.write(`\nVault check: the hooks use the same vault you selected in OAuth, ${vaultLabel(binding)}.\n`);
131
+ else if (binding && binding.status === 'mixed') process.stdout.write(`\nVault check: ${binding.message}\n`);
132
+ else if (binding && binding.status === 'unknown') process.stdout.write(`\nVault check skipped (${binding.message}). Run doctor later to confirm one vault.\n`);
133
+ if (result.hookMode === 'mcp') {
134
+ process.stdout.write('\nAutomatic memory: before every prompt Codex searches your vault and adds what it finds as context; after every turn it saves your message and its final response (never tool calls, tool output or intermediate steps). No API key needed.\n');
135
+ process.stdout.write('\nNext:\n');
136
+ if (!result.adoptedServer) process.stdout.write(' 1. Sign in: Codex app Settings > Plugins > memoryrouter > Authenticate (or run: codex mcp login memoryrouter)\n');
137
+ process.stdout.write(` ${result.adoptedServer ? '1' : '2'}. Trust the MemoryRouter hooks once: Codex app Settings > Hooks (or /hooks in the CLI)\n`);
138
+ process.stdout.write(` ${result.adoptedServer ? '2' : '3'}. Start a New chat\n`);
139
+ } else if (result.auth === 'oauth') {
140
+ const pick = binding && binding.vault ? ` and select the vault ${vaultLabel(binding)} (the one your hooks use)` : '';
141
+ process.stdout.write(`\nNext: codex mcp login memoryrouter${pick}\nThen open Codex, run /hooks and trust the MemoryRouter hooks, and check /mcp verbose.\n`);
142
+ }
143
+ else process.stdout.write('\nNext: open Codex, run /hooks, and trust the MemoryRouter hooks.\n');
144
+ if (result.keySource === 'claude-code-config') process.stdout.write('\nAutomatic hooks reuse your Claude Code Memory Key (~/.memoryrouter/config.json), so both agents share the same vault.\n');
145
+ if (!result.automaticMemory && options.hooks !== false) process.stdout.write('\nRemote MCP tools are configured, but automatic hooks are fail-open/no-op until MEMORYROUTER_API_KEY is available or init is re-run with --api-key-stdin.\n');
146
+ const scopeArgs = result.layout.scope === 'project' ? `--scope project --project-dir ${JSON.stringify(result.layout.root)}` : '--scope user';
147
+ process.stdout.write(`\nVerify: npx -y memoryrouter-codex@${INTEGRATION_VERSION} doctor ${scopeArgs}\n`);
148
+ process.stdout.write('One memory across every session, repo and tool: Codex, Claude Code (memoryrouter-claude) and the MemoryRouter MCP all use the same vault.\n');
122
149
  return;
123
150
  }
124
151
  if (command === 'doctor') {
package/lib/hooks.cjs CHANGED
@@ -5,7 +5,14 @@ const path = require('path');
5
5
  const { atomicWrite, readText } = require('./managed.cjs');
6
6
  const { INTEGRATION_VERSION } = require('../runtime/constants.cjs');
7
7
 
8
+ // The hidden Codex lifecycle tools on the MemoryRouter MCP server
9
+ // (mcp-server/src/tools/codex-hooks.ts).
10
+ const MCP_HOOK_TOOLS = new Set(['codex_turn_start', 'codex_turn_end', 'codex_session_start']);
11
+
8
12
  function isOwnedHook(handler) {
13
+ if (handler?.type === 'mcp_tool') {
14
+ return handler.server === 'memoryrouter' && MCP_HOOK_TOOLS.has(handler.tool);
15
+ }
9
16
  return handler?.type === 'command' && typeof handler.command === 'string' &&
10
17
  (/memoryrouter[\\/]runtime[\\/]hook\.cjs/.test(handler.command) || /memoryrouter-codex(?:@[^ ]+)?\s+hook\b/.test(handler.command));
11
18
  }
@@ -49,16 +56,47 @@ function managedGroups(runtimeDir, scope) {
49
56
  ...(statusMessage ? { statusMessage } : {}),
50
57
  ...(additionalContextLimit ? { additionalContextLimit } : {}),
51
58
  });
59
+ // Per turn: recall before the prompt, save the user's prompt and the final
60
+ // assistant response after the turn. Nothing in between (no tool calls, tool
61
+ // output, reasoning, subagent output). SessionEnd only saves the prompt of a
62
+ // last turn that never finished (interrupted), see runtime/hook.cjs flush.
52
63
  return {
53
64
  SessionStart: [{ matcher: 'startup|resume|clear|compact', hooks: [handler('session-start', 8, 'Recalling MemoryRouter context', 3500)] }],
54
- UserPromptSubmit: [{ hooks: [handler('inject', 8, 'Searching project memory', 3500)] }],
55
- Stop: [{ hooks: [handler('capture', 18, 'Saving durable project memory')] }],
56
- SubagentStop: [{ hooks: [handler('capture-subagent', 15, 'Saving durable subagent outcome')] }],
57
- PreCompact: [{ matcher: 'manual|auto', hooks: [handler('flush', 18, 'Saving memory before compaction')] }],
65
+ UserPromptSubmit: [{ hooks: [handler('inject', 8, 'Searching MemoryRouter', 3500)] }],
66
+ Stop: [{ hooks: [handler('capture', 18, 'Saving to MemoryRouter')] }],
58
67
  SessionEnd: [{ hooks: [handler('flush', 3)] }],
59
68
  };
60
69
  }
61
70
 
71
+ /**
72
+ * OAuth hooks: no API key and no local runtime. Codex calls hidden tools on the
73
+ * already-authenticated `memoryrouter` MCP server (`type: "mcp_tool"`), with
74
+ * the user's OAuth token and the vault picked during sign-in.
75
+ * UserPromptSubmit -> codex_turn_start: recall for this prompt (returned as
76
+ * developer context), then save the prompt.
77
+ * Stop -> codex_turn_end: save the final assistant message.
78
+ * SessionStart (compact) -> codex_session_start: restore context right after
79
+ * Codex compacts a long chat.
80
+ * Codex does not support mcp_tool hooks on SessionEnd; none is needed.
81
+ */
82
+ function mcpManagedGroups(serverName = 'memoryrouter') {
83
+ const turn = { session_id: '${session_id}', turn_id: '${turn_id}', cwd: '${cwd}' };
84
+ return {
85
+ // Only after compaction: on startup/resume/clear the first prompt's
86
+ // codex_turn_start recall already injects context (no double injection).
87
+ SessionStart: [{
88
+ matcher: 'compact',
89
+ hooks: [{ type: 'mcp_tool', server: serverName, tool: 'codex_session_start', input: { session_id: '${session_id}', cwd: '${cwd}', source: '${source}' }, timeout: 12, statusMessage: 'Recalling MemoryRouter context' }],
90
+ }],
91
+ UserPromptSubmit: [{
92
+ hooks: [{ type: 'mcp_tool', server: serverName, tool: 'codex_turn_start', input: { prompt: '${prompt}', ...turn }, timeout: 12, statusMessage: 'Searching MemoryRouter' }],
93
+ }],
94
+ Stop: [{
95
+ hooks: [{ type: 'mcp_tool', server: serverName, tool: 'codex_turn_end', input: { last_assistant_message: '${last_assistant_message}', ...turn }, timeout: 15, statusMessage: 'Saving to MemoryRouter' }],
96
+ }],
97
+ };
98
+ }
99
+
62
100
  function parseHooks(file) {
63
101
  const raw = readText(file);
64
102
  if (!raw.trim()) return { description: 'Lifecycle hooks for this Codex configuration.', hooks: {} };
@@ -71,18 +109,19 @@ function parseHooks(file) {
71
109
  }
72
110
  }
73
111
 
74
- function installHooks(file, runtimeDir, scope) {
112
+ function installHooks(file, runtimeDir, scope, options = {}) {
75
113
  const current = parseHooks(file);
76
114
  const clean = cleanOwnedHooks(current);
77
115
  clean.description = clean.description || 'Lifecycle hooks for this Codex configuration.';
78
116
  clean.hooks = clean.hooks || {};
79
- for (const [event, groups] of Object.entries(managedGroups(runtimeDir, scope))) {
117
+ const groupsToInstall = options.mode === 'mcp' ? mcpManagedGroups(options.serverName) : managedGroups(runtimeDir, scope);
118
+ for (const [event, groups] of Object.entries(groupsToInstall)) {
80
119
  clean.hooks[event] = [...(clean.hooks[event] || []), ...groups];
81
120
  }
82
121
  const rendered = `${JSON.stringify(clean, null, 2)}\n`;
83
122
  const previous = readText(file);
84
123
  atomicWrite(file, rendered);
85
- return { changed: rendered !== previous, file, events: Object.keys(managedGroups(runtimeDir, scope)) };
124
+ return { changed: rendered !== previous, file, mode: options.mode === 'mcp' ? 'mcp' : 'command', events: Object.keys(groupsToInstall) };
86
125
  }
87
126
 
88
127
  function uninstallHooks(file) {
@@ -99,4 +138,4 @@ function uninstallHooks(file) {
99
138
  return { changed: true, file };
100
139
  }
101
140
 
102
- module.exports = { isOwnedHook, cleanOwnedHooks, commandFor, managedGroups, parseHooks, installHooks, uninstallHooks };
141
+ module.exports = { isOwnedHook, cleanOwnedHooks, commandFor, managedGroups, mcpManagedGroups, MCP_HOOK_TOOLS, parseHooks, installHooks, uninstallHooks };
package/lib/installer.cjs CHANGED
@@ -4,14 +4,14 @@ const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
6
  const { execFileSync } = require('child_process');
7
- const { codexHome, dataHome, loadConfig, saveConfig, configPath } = require('../runtime/config.cjs');
7
+ const { codexHome, dataHome, loadConfig, saveConfig, configPath, getApiKey, apiKeySource } = require('../runtime/config.cjs');
8
8
  const { resolveProject } = require('../runtime/project.cjs');
9
9
  const { MCP_ENDPOINT, INTEGRATION_VERSION } = require('../runtime/constants.cjs');
10
10
  const {
11
11
  installToml, uninstallToml, installAgents, uninstallAgents, readText, replaceBlock,
12
- hasUnmanagedMemoryRouterTable, TOML_START, TOML_END, AGENTS_START, AGENTS_END,
12
+ hasUnmanagedMemoryRouterTable, externalRemoteServerName, TOML_START, TOML_END, AGENTS_START, AGENTS_END,
13
13
  } = require('./managed.cjs');
14
- const { installHooks, uninstallHooks, isOwnedHook, managedGroups, parseHooks } = require('./hooks.cjs');
14
+ const { installHooks, uninstallHooks, isOwnedHook, managedGroups, mcpManagedGroups, parseHooks } = require('./hooks.cjs');
15
15
 
16
16
  function resolveLayout(options = {}) {
17
17
  const env = options.env || process.env;
@@ -54,6 +54,9 @@ function preflight(layout, options = {}) {
54
54
  const configText = readText(layout.configFile);
55
55
  // Validate marker pairs before any file is changed.
56
56
  replaceBlock(configText, TOML_START, TOML_END, '');
57
+ // A remote MemoryRouter server the user added themselves (Codex app:
58
+ // Settings > Plugins > Add MCP server) is adopted as is, never an error.
59
+ if (options.auth !== 'api-key' && externalRemoteServerName(configText, MCP_ENDPOINT)) return;
57
60
  if (hasUnmanagedMemoryRouterTable(configText) && !options.force) {
58
61
  throw new Error(`An unmanaged [mcp_servers.memoryrouter] table already exists in ${layout.configFile}. Re-run with --force after reviewing the migration backup.`);
59
62
  }
@@ -61,16 +64,41 @@ function preflight(layout, options = {}) {
61
64
  if (options.agents !== false) replaceBlock(readText(layout.agentsFile), AGENTS_START, AGENTS_END, '');
62
65
  }
63
66
 
67
+ /**
68
+ * How the automatic hooks reach MemoryRouter.
69
+ * - 'mcp': Codex `mcp_tool` hooks call MemoryRouter's hidden lifecycle tools
70
+ * over the signed-in OAuth MCP connection. No API key on disk.
71
+ * - 'command': the local Node runtime with a Memory Key (api-key mode, or
72
+ * OAuth users who also configured a key, e.g. shared with Claude Code).
73
+ */
74
+ function resolveHookMode(options, auth, env) {
75
+ if (options.hookMode === 'mcp' || options.hookMode === 'command') return options.hookMode;
76
+ if (auth === 'api-key') return 'command';
77
+ const key = options.apiKey || getApiKey(env, loadConfig(options.env));
78
+ return key ? 'command' : 'mcp';
79
+ }
80
+
64
81
  function init(options = {}) {
65
82
  const layout = resolveLayout(options);
66
83
  const auth = options.auth || 'oauth';
67
84
  if (!['oauth', 'api-key'].includes(auth)) throw new Error('--auth must be oauth or api-key');
68
- preflight(layout, options);
85
+ if (options.hookMode && !['mcp', 'command'].includes(options.hookMode)) throw new Error('--hooks-via must be mcp or command');
86
+ const env = options.env || process.env;
87
+ preflight(layout, { ...options, auth });
88
+ const hookMode = resolveHookMode(options, auth, env);
89
+ if (hookMode === 'mcp' && auth === 'api-key') throw new Error('--hooks-via mcp needs the OAuth MCP server (--auth oauth).');
69
90
  copyRuntime(layout.runtimeDir);
70
91
  const results = [];
71
- results.push(installToml(layout.configFile, { auth, scope: layout.scope, runtimeDir: layout.runtimeDir, force: options.force }));
72
- if (options.hooks !== false) results.push(installHooks(layout.hooksFile, layout.runtimeDir, layout.scope));
73
- if (options.agents !== false) results.push(installAgents(layout.agentsFile));
92
+ const adoptedServer = auth === 'oauth' ? externalRemoteServerName(readText(layout.configFile), MCP_ENDPOINT) : null;
93
+ if (adoptedServer) {
94
+ // Leave the user's own server table (and its sign-in) untouched.
95
+ results.push({ changed: false, file: layout.configFile, adopted: adoptedServer });
96
+ } else {
97
+ results.push(installToml(layout.configFile, { auth, scope: layout.scope, runtimeDir: layout.runtimeDir, force: options.force }));
98
+ }
99
+ const serverName = adoptedServer || 'memoryrouter';
100
+ if (options.hooks !== false) results.push(installHooks(layout.hooksFile, layout.runtimeDir, layout.scope, { mode: hookMode, serverName }));
101
+ if (options.agents !== false) results.push(installAgents(layout.agentsFile, { hookMode }));
74
102
 
75
103
  if (options.apiKey) {
76
104
  const current = loadConfig(options.env);
@@ -81,6 +109,9 @@ function init(options = {}) {
81
109
  installedAt: new Date().toISOString(),
82
110
  scope: layout.scope,
83
111
  auth,
112
+ hookMode,
113
+ serverName,
114
+ adoptedServer: Boolean(adoptedServer),
84
115
  configFile: layout.configFile,
85
116
  hooksFile: options.hooks === false ? null : layout.hooksFile,
86
117
  agentsFile: options.agents === false ? null : layout.agentsFile,
@@ -90,7 +121,16 @@ function init(options = {}) {
90
121
  fs.mkdirSync(manifestDir, { recursive: true, mode: 0o700 });
91
122
  const manifestName = Buffer.from(`${layout.scope}:${layout.root}`).toString('base64url');
92
123
  fs.writeFileSync(path.join(manifestDir, `${manifestName}.json`), `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
93
- return { layout, auth, results, automaticMemory: Boolean(options.apiKey || process.env.MEMORYROUTER_API_KEY || loadConfig(options.env).apiKey) };
124
+ return {
125
+ layout,
126
+ auth,
127
+ hookMode,
128
+ serverName,
129
+ adoptedServer,
130
+ results,
131
+ automaticMemory: options.hooks !== false && (hookMode === 'mcp' || Boolean(options.apiKey || getApiKey(env, loadConfig(options.env)))),
132
+ keySource: options.apiKey ? 'codex-config' : apiKeySource(env, loadConfig(options.env)),
133
+ };
94
134
  }
95
135
 
96
136
  function uninstall(options = {}) {
@@ -168,7 +208,9 @@ async function doctor(options = {}) {
168
208
  else if (!versionAtLeast(codex.version, [0, 145, 0])) addCheck(checks, 'codex-version', 'error', `${codex.output} is too old for the lifecycle hooks used here. Upgrade to Codex 0.145.0 or newer.`);
169
209
  else addCheck(checks, 'codex-version', 'pass', `${codex.output} supports current hooks and remote MCP configuration.`);
170
210
 
171
- addCheck(checks, 'mcp-config', configText.includes(TOML_START) && configText.includes('[mcp_servers.memoryrouter]') ? 'pass' : 'error',
211
+ const adopted = externalRemoteServerName(configText, MCP_ENDPOINT);
212
+ if (adopted) addCheck(checks, 'mcp-config', 'pass', `MemoryRouter MCP server "${adopted}" (added in the Codex app or by hand) is configured.`);
213
+ else addCheck(checks, 'mcp-config', configText.includes(TOML_START) && configText.includes('[mcp_servers.memoryrouter]') ? 'pass' : 'error',
172
214
  configText.includes(TOML_START) ? 'Managed MemoryRouter MCP configuration is present.' : 'Managed MemoryRouter MCP configuration is missing.');
173
215
  addCheck(checks, 'runtime', runtimeVersion === INTEGRATION_VERSION ? 'pass' : 'error',
174
216
  runtimeVersion ? `Installed runtime version is ${runtimeVersion}; package version is ${INTEGRATION_VERSION}.` : 'Managed runtime is missing.');
@@ -176,22 +218,40 @@ async function doctor(options = {}) {
176
218
  let hooks = null;
177
219
  try { hooks = hooksText ? JSON.parse(hooksText) : null; } catch { /* handled */ }
178
220
  const owned = hooks ? Object.values(hooks.hooks || {}).flat().flatMap(group => group.hooks || []).filter(isOwnedHook) : [];
179
- const expectedOwned = Object.values(managedGroups(layout.runtimeDir, layout.scope)).flat().flatMap(group => group.hooks || []).length;
180
- addCheck(checks, 'hooks', owned.length === expectedOwned ? 'pass' : 'error', owned.length ? `${owned.length} of ${expectedOwned} managed lifecycle hooks found.` : 'Managed lifecycle hooks are missing or hooks.json is invalid.');
221
+ const mcpHooks = owned.filter(handler => handler.type === 'mcp_tool');
222
+ const expectedOwned = mcpHooks.length
223
+ ? Object.values(mcpManagedGroups()).flat().flatMap(group => group.hooks || []).length
224
+ : Object.values(managedGroups(layout.runtimeDir, layout.scope)).flat().flatMap(group => group.hooks || []).length;
225
+ addCheck(checks, 'hooks', owned.length === expectedOwned ? 'pass' : 'error', owned.length
226
+ ? `${owned.length} of ${expectedOwned} managed lifecycle hooks found${mcpHooks.length ? ' (over the signed-in MCP connection, no API key needed)' : ''}. Trust them once in Codex: app Settings > Hooks, or /hooks in the CLI.`
227
+ : 'Managed lifecycle hooks are missing or hooks.json is invalid.');
181
228
  addCheck(checks, 'agents', agentsText.includes(AGENTS_START) ? 'pass' : 'warn', agentsText.includes(AGENTS_START) ? 'Managed AGENTS.md guidance is present.' : 'Managed AGENTS.md guidance is missing.');
182
229
 
183
230
  const cfg = loadConfig(options.env);
184
- const key = process.env.MEMORYROUTER_API_KEY || cfg.apiKey;
185
- if (!key) addCheck(checks, 'automatic-auth', 'warn', 'No MemoryRouter API key is available. Remote OAuth tools can work after `codex mcp login memoryrouter`, but automatic hook retrieval/capture is disabled.');
231
+ const keyEnv = options.env || process.env;
232
+ const key = getApiKey(keyEnv, cfg);
233
+ const keyFrom = apiKeySource(keyEnv, cfg);
234
+ if (!key && mcpHooks.length) addCheck(checks, 'automatic-auth', 'pass', 'Automatic recall and saving use your MemoryRouter sign-in (OAuth). No API key needed.');
235
+ else if (!key) addCheck(checks, 'automatic-auth', 'warn', 'No MemoryRouter API key is available. Remote OAuth tools can work after `codex mcp login memoryrouter`, but automatic hook retrieval/capture is disabled.');
186
236
  else {
187
237
  try {
188
238
  const { stats } = require('../runtime/api.cjs');
189
239
  const response = options.offline ? { ok: true, status: 0 } : await stats(key, { apiBase: cfg.apiBase, timeoutMs: 6000, retries: 0 });
190
- addCheck(checks, 'automatic-auth', response.ok ? 'pass' : 'error', options.offline ? 'API key is configured (network check skipped).' : response.ok ? 'API key is valid; automatic retrieval/capture is active.' : `API key validation failed (HTTP ${response.status}).`);
240
+ addCheck(checks, 'automatic-auth', response.ok ? 'pass' : 'error', options.offline ? 'API key is configured (network check skipped).' : response.ok ? `API key is valid${keyFrom === 'claude-code-config' ? ' (reusing the Claude Code key in ~/.memoryrouter/config.json)' : ''}; automatic retrieval/capture is active.` : `API key validation failed (HTTP ${response.status}).`);
191
241
  } catch (error) { addCheck(checks, 'automatic-auth', 'error', `API key validation failed: ${error.message}`); }
242
+ if (!options.offline) {
243
+ const { checkVaultBinding, vaultLabel } = require('../runtime/vault-binding.cjs');
244
+ const binding = await checkVaultBinding(key, { env: options.env || process.env, timeoutMs: 5000 });
245
+ const levels = { match: 'pass', mismatch: 'error', mixed: 'warn', 'not-connected': 'info', unknown: 'warn' };
246
+ addCheck(checks, 'one-vault', levels[binding.status] || 'warn',
247
+ binding.status === 'match' ? `Hooks and your OAuth connection use the same vault ${vaultLabel(binding)}.`
248
+ : binding.status === 'not-connected' ? `No MemoryRouter OAuth connection yet. When you run \`codex mcp login memoryrouter\`, select ${vaultLabel(binding)}.`
249
+ : binding.status === 'unknown' ? `Vault match not checked (${binding.message}).`
250
+ : binding.message);
251
+ }
192
252
  }
193
253
 
194
- if (configText.includes('auth = "oauth"')) {
254
+ if (configText.includes('auth = "oauth"') || adopted) {
195
255
  if (options.offline) {
196
256
  addCheck(checks, 'oauth-discovery', 'warn', 'OAuth discovery check skipped in offline mode.');
197
257
  addCheck(checks, 'oauth-session', 'info', 'Browser consent/token issuance is user-specific. Run `codex mcp login memoryrouter`; then `/mcp verbose`. Doctor does not claim login completion without interactive consent.');
@@ -222,10 +282,11 @@ async function doctor(options = {}) {
222
282
  ? `Codex native Memories are enabled${externalDisabled ? ' and external-context generation is disabled.' : '. Consider [memories] disable_on_external_context = true to avoid re-extracting MemoryRouter-assisted chats.'}`
223
283
  : 'Codex native Memories are off or configured in another layer. This integration does not overwrite native-memory settings.');
224
284
 
225
- const project = resolveProject(layout.root);
226
- addCheck(checks, 'project-identity', project.portable ? 'pass' : 'warn', project.portable
227
- ? `Project handle ${project.handle} is derived from a canonical git remote and is stable across worktrees/machines.`
228
- : `Project handle ${project.handle} falls back to a local ${project.source}; add an origin remote for cross-machine identity.`);
285
+ // One vault per user: report the scope plainly, plus the repository label
286
+ // that captures from this directory carry (metadata only, never a wall).
287
+ const identityDir = layout.scope === 'user' ? (options.projectDir || process.cwd()) : layout.root;
288
+ const project = resolveProject(identityDir, { env: options.env, config: loadConfig(options.env) });
289
+ addCheck(checks, 'memory-scope', 'pass', `One vault for everything: every Codex session, every repository, Claude Code (memoryrouter-claude), and the MemoryRouter MCP read and write the same memory. Captures from this directory are tagged ${project.handle} (${project.source}) so recall can label them; the tag never limits what is recalled.`);
229
290
  addCheck(checks, 'cloud-surface', layout.scope === 'project' ? 'info' : 'warn', layout.scope === 'project'
230
291
  ? 'Project config/hooks can travel with the repository, but cloud environments must trust the project, install Node, and provide MEMORYROUTER_API_KEY. ChatGPT web does not consume local Codex config; use a hosted plugin/connector there.'
231
292
  : 'User-scope config covers local CLI, desktop app, and IDE on this host. Codex cloud and ChatGPT web do not inherit this machine-local config.');
package/lib/managed.cjs CHANGED
@@ -53,6 +53,26 @@ function removeUnmanagedMemoryRouterTables(text) {
53
53
  return kept.join('\n').replace(/\n{3,}/g, '\n\n');
54
54
  }
55
55
 
56
+ /**
57
+ * The MCP server name the user already connected to MemoryRouter's remote
58
+ * endpoint outside our managed block (for example the Codex app's
59
+ * Settings > Plugins > Add MCP server), or null. Returns the first
60
+ * `[mcp_servers.<name>]` whose `url` is the MemoryRouter MCP endpoint.
61
+ */
62
+ function externalRemoteServerName(text, endpoint) {
63
+ const stripped = replaceBlock(text, TOML_START, TOML_END, '');
64
+ const lines = stripped.split('\n');
65
+ let current = null;
66
+ for (const line of lines) {
67
+ const table = line.match(/^\s*\[mcp_servers\.("?)([^\]".]+)\1\]\s*(?:#.*)?$/);
68
+ if (table) { current = table[2]; continue; }
69
+ if (/^\s*\[/.test(line)) { current = null; continue; }
70
+ const url = line.match(/^\s*url\s*=\s*"([^"]+)"/);
71
+ if (current && url && url[1].replace(/\/+$/, '') === endpoint.replace(/\/+$/, '')) return current;
72
+ }
73
+ return null;
74
+ }
75
+
56
76
  function tomlString(value) { return JSON.stringify(String(value)); }
57
77
 
58
78
  function mcpBlock({ auth = 'oauth', scope = 'user', runtimeDir }) {
@@ -129,13 +149,16 @@ function uninstallToml(file) {
129
149
  return { changed: true, file };
130
150
  }
131
151
 
132
- function agentsBlock() {
133
- return `## MemoryRouter memory\n\n- MemoryRouter is the durable cross-machine, cross-tool, and team/auditable memory layer. Codex native Memories may remain enabled as the local personal recall layer.\n- Automatic hooks search project-scoped memory before each prompt and capture completed turns when a MemoryRouter API key is configured.\n- For explicit recall, search MemoryRouter before answering questions about prior decisions, preferences, incidents, or unfinished work. Depending on auth mode, the tool is named \`search_memories\` or \`memory_search\`.\n- Store only concise, durable decisions, preferences, facts, and incident outcomes. Depending on auth mode, use \`store_memory\` or \`memory_store\`. Do not duplicate a completed turn that hooks already captured.\n- Keep and cite returned \`mrh_...\` handles when a specific memory must be referenced later.\n- Project scope is derived from the canonical git remote when available, so worktrees and machines share identity without uploading local paths.\n- If MemoryRouter is offline, continue the task without memory; never block work on recall or capture.`;
152
+ function agentsBlock(options = {}) {
153
+ const automatic = options.hookMode === 'mcp'
154
+ ? '- Automatic hooks (over the signed-in MemoryRouter connection) search the vault before each prompt and add what they find as context, then save each completed turn: the user\'s message and your final response only, never tool calls, tool output or intermediate steps.\n'
155
+ : '- Automatic hooks search the vault before each prompt and save each completed turn (the user\'s message and your final response only) when a MemoryRouter API key is configured.\n';
156
+ return `## MemoryRouter memory\n\n- MemoryRouter is the durable cross-machine, cross-tool, and team/auditable memory layer. Codex native Memories may remain enabled as the local personal recall layer.\n${automatic}- For explicit recall, search MemoryRouter before answering questions about prior decisions, preferences, incidents, or unfinished work. Depending on auth mode, the tool is named \`search_memories\` or \`memory_search\`.\n- Store only concise, durable decisions, preferences, facts, and incident outcomes. Depending on auth mode, use \`store_memory\` or \`memory_store\`. Do not duplicate a completed turn that hooks already captured.\n- Keep and cite returned \`mrh_...\` handles when a specific memory must be referenced later.\n- Project scope is derived from the canonical git remote when available, so worktrees and machines share identity without uploading local paths. It is the same project memory Claude Code uses through memoryrouter-claude, so decisions made in either agent are available in both.\n- If MemoryRouter is offline, continue the task without memory; never block work on recall or capture.`;
134
157
  }
135
158
 
136
- function installAgents(file) {
159
+ function installAgents(file, options = {}) {
137
160
  const current = readText(file);
138
- const updated = replaceBlock(current, AGENTS_START, AGENTS_END, agentsBlock());
161
+ const updated = replaceBlock(current, AGENTS_START, AGENTS_END, agentsBlock(options));
139
162
  atomicWrite(file, updated);
140
163
  return { changed: updated !== current, file };
141
164
  }
@@ -158,6 +181,7 @@ module.exports = {
158
181
  atomicWrite,
159
182
  replaceBlock,
160
183
  hasUnmanagedMemoryRouterTable,
184
+ externalRemoteServerName,
161
185
  removeUnmanagedMemoryRouterTables,
162
186
  mcpBlock,
163
187
  installToml,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memoryrouter-codex",
3
- "version": "1.6.1",
4
- "description": "Automatic, project-scoped MemoryRouter memory for OpenAI Codex",
3
+ "version": "1.8.0",
4
+ "description": "Automatic MemoryRouter memory for OpenAI Codex: one vault shared by every session, repo and tool, including Claude Code",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
7
7
  "bin": {