Merge pull request #2342 from thedotmack/fix-and-ship-codex-mem-search-access
Fix Codex mem-search MCP startup
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
"name": "claude-mem",
|
"name": "claude-mem",
|
||||||
"source": {
|
"source": {
|
||||||
"source": "local",
|
"source": "local",
|
||||||
"path": "./"
|
"path": "./plugin"
|
||||||
},
|
},
|
||||||
"policy": {
|
"policy": {
|
||||||
"installation": "AVAILABLE",
|
"installation": "AVAILABLE",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"command": "sh",
|
"command": "sh",
|
||||||
"args": [
|
"args": [
|
||||||
"-c",
|
"-c",
|
||||||
"_C=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"; _E=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; _P=$({ [ -n \"$_E\" ] && printf '%s\\n' \"$_E\"; ls -dt \"$_C/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ 2>/dev/null; printf '%s\\n' \"$_C/plugins/marketplaces/thedotmack/plugin\"; } | while IFS= read -r _R; do _R=\"${_R%/}\"; [ -d \"$_R/plugin/scripts\" ] && _Q=\"$_R/plugin\" || _Q=\"$_R\"; [ -f \"$_Q/scripts/mcp-server.cjs\" ] && { printf '%s\\n' \"$_Q\"; break; }; done); [ -n \"$_P\" ] || { echo \"claude-mem: mcp server not found\" >&2; exit 1; }; exec node \"$_P/scripts/mcp-server.cjs\""
|
"_C=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"; _E=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; _P=$({ [ -n \"$_E\" ] && printf '%s\\n' \"$_E\"; printf '%s\\n' \"$PWD/plugin\" \"$PWD\"; ls -dt \"$HOME/.codex/plugins/cache/claude-mem-local/claude-mem\"/[0-9]*/ \"$HOME/.codex/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ \"$_C/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ 2>/dev/null; printf '%s\\n' \"$_C/plugins/marketplaces/thedotmack/plugin\"; } | while IFS= read -r _R; do _R=\"${_R%/}\"; [ -d \"$_R/plugin/scripts\" ] && _Q=\"$_R/plugin\" || _Q=\"$_R\"; [ -f \"$_Q/scripts/mcp-server.cjs\" ] && { printf '%s\\n' \"$_Q\"; break; }; done); [ -n \"$_P\" ] || { echo \"claude-mem: mcp server not found\" >&2; exit 1; }; exec node \"$_P/scripts/mcp-server.cjs\""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
# Codex Plugin Version Mismatch Investigation Plan
|
||||||
|
|
||||||
|
Date: 2026-05-06
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Codex is still exposing `claude-mem` from:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/Users/alexnewman/.codex/plugins/cache/thedotmack/claude-mem/12.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
That cache entry is the source of the `claude-mem:...` skills loaded in this Codex session. The working tree and the Codex marketplace clone both advertise `12.7.2`, but the enabled Codex plugin points at the old installed cache. This is not a model-memory issue.
|
||||||
|
|
||||||
|
The likely root cause is an incomplete migration from marketplace registration to a first-class Codex plugin install. The current installer registers the marketplace, but it does not verify that the actual enabled plugin cache was installed or upgraded to the current `.codex-plugin` bundle.
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
- Current repository metadata is `12.7.2`:
|
||||||
|
- `package.json`
|
||||||
|
- `.codex-plugin/plugin.json`
|
||||||
|
- `plugin/.codex-plugin/plugin.json`
|
||||||
|
- `plugin/package.json`
|
||||||
|
|
||||||
|
- Codex marketplace source is current:
|
||||||
|
- `/Users/alexnewman/.codex/config.toml` contains `[marketplaces.claude-mem-local]`
|
||||||
|
- `last_updated = "2026-05-06T23:13:59Z"`
|
||||||
|
- `last_revision = "bb3dbfdb5ae92b55b7e4686e4904995184261232"`
|
||||||
|
- `/Users/alexnewman/.codex/.tmp/marketplaces/claude-mem-local/package.json` is `12.7.2`
|
||||||
|
- `/Users/alexnewman/.codex/.tmp/marketplaces/claude-mem-local/.codex-plugin/plugin.json` is `12.7.2`
|
||||||
|
|
||||||
|
- Active enabled plugin state is still old:
|
||||||
|
- `/Users/alexnewman/.codex/config.toml` contains `[plugins."claude-mem@thedotmack"] enabled = true`
|
||||||
|
- The only `claude-mem` plugin cache under `~/.codex/plugins/cache/thedotmack/claude-mem` is `12.3.1`
|
||||||
|
- `/Users/alexnewman/.codex/plugins/cache/thedotmack/claude-mem/12.3.1/package.json` is `12.3.1`
|
||||||
|
- `/Users/alexnewman/.codex/plugins/cache/thedotmack/claude-mem/12.3.1/.install-version` records `{"version":"12.3.1", ...}`
|
||||||
|
|
||||||
|
- The active cache is not shaped like the new first-class Codex bundle:
|
||||||
|
- It has `.claude-plugin/plugin.json`
|
||||||
|
- It does not have `.codex-plugin/plugin.json`
|
||||||
|
- It does not have `hooks/codex-hooks.json`
|
||||||
|
- Its `.mcp.json` still uses the old `bun` command with `"${CLAUDE_PLUGIN_ROOT}/scripts/mcp-server.cjs"`
|
||||||
|
|
||||||
|
- Current Codex CLI capability is limited:
|
||||||
|
- `codex-cli 0.128.0`
|
||||||
|
- `codex plugin marketplace` exposes `add`, `upgrade`, and `remove`
|
||||||
|
- There is no CLI `plugin list` or `plugin install` subcommand in this build
|
||||||
|
|
||||||
|
- Current installer code only registers a marketplace:
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:188` prints the marketplace root
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:189` runs `codex plugin marketplace add <root>`
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:200` through `src/services/integrations/CodexCliInstaller.ts:203` tells the user to open `/plugins` and install manually
|
||||||
|
- `src/npx-cli/commands/install.ts:271` through `src/npx-cli/commands/install.ts:281` reports success as "hooks marketplace registered", not "plugin installed"
|
||||||
|
|
||||||
|
## Working Theory
|
||||||
|
|
||||||
|
There are two independent states:
|
||||||
|
|
||||||
|
1. Marketplace source state: current and registered as `claude-mem-local`.
|
||||||
|
2. Installed plugin cache state: stale and enabled as `claude-mem@thedotmack`.
|
||||||
|
|
||||||
|
Codex loads skills, hooks, and MCP metadata from the installed plugin cache, not directly from the marketplace source. Since the installed cache is still `12.3.1`, every new Codex session sees `claude-mem` as `12.3.1`, even though the marketplace clone is already at `12.7.2`.
|
||||||
|
|
||||||
|
The `claude-mem@thedotmack` plugin ID also suggests this cache came from an older GitHub marketplace install path, while the current installer registers `claude-mem-local`. That mismatch needs to be handled explicitly during repair and install.
|
||||||
|
|
||||||
|
## Phase 0: Reproduce And Baseline
|
||||||
|
|
||||||
|
What to do:
|
||||||
|
|
||||||
|
- Capture a clean before-state snapshot:
|
||||||
|
- `codex --version`
|
||||||
|
- `sed -n '1,220p' ~/.codex/config.toml`
|
||||||
|
- `find ~/.codex/plugins/cache -maxdepth 5 -type f \( -name 'plugin.json' -o -name 'package.json' -o -name '.mcp.json' -o -name 'codex-hooks.json' \) -print`
|
||||||
|
- `find ~/.codex/plugins/cache/thedotmack/claude-mem -maxdepth 2 -type d -print`
|
||||||
|
|
||||||
|
- Confirm which paths Codex injects into the session skill list:
|
||||||
|
- Start a fresh Codex session.
|
||||||
|
- Inspect the available skills list for `claude-mem:` paths.
|
||||||
|
- Expected current bad path: `~/.codex/plugins/cache/thedotmack/claude-mem/12.3.1/skills`.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- The before-state snapshot shows the stale cache and current marketplace clone side by side.
|
||||||
|
- The fresh session still reports `12.3.1` before remediation.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- Do not delete `~/.codex/plugins/cache` blindly.
|
||||||
|
- Do not edit unrelated `~/.codex/config.toml` project trust settings.
|
||||||
|
- Do not assume `codex plugin marketplace upgrade` upgrades the installed plugin cache until verified.
|
||||||
|
|
||||||
|
## Phase 1: Local Recovery Procedure
|
||||||
|
|
||||||
|
What to do:
|
||||||
|
|
||||||
|
- Back up the current Codex plugin state:
|
||||||
|
- `cp ~/.codex/config.toml ~/.codex/config.toml.bak-$(date +%Y%m%d-%H%M%S)`
|
||||||
|
- Archive or copy `~/.codex/plugins/cache/thedotmack/claude-mem/12.3.1`
|
||||||
|
|
||||||
|
- Remove the stale enabled plugin state through supported UI where possible:
|
||||||
|
- Open Codex.
|
||||||
|
- Run `/plugins`.
|
||||||
|
- Disable or uninstall `claude-mem@thedotmack` if it appears.
|
||||||
|
|
||||||
|
- Register or refresh the current marketplace:
|
||||||
|
- `codex plugin marketplace upgrade claude-mem-local`
|
||||||
|
- If needed, re-add from the durable local marketplace root produced by the installer.
|
||||||
|
|
||||||
|
- Install `claude-mem` from the `claude-mem (local)` marketplace in `/plugins`.
|
||||||
|
|
||||||
|
- Restart Codex.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- `~/.codex/plugins/cache` contains a `claude-mem` cache with `.codex-plugin/plugin.json`.
|
||||||
|
- The active plugin cache has `version: 12.7.2`.
|
||||||
|
- The active plugin cache has `hooks/codex-hooks.json`.
|
||||||
|
- The active plugin cache `.mcp.json` uses the portable `sh -c` wrapper from the current repo.
|
||||||
|
- A fresh Codex session lists `claude-mem:` skills from the new cache, not `12.3.1`.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- Do not manually copy the repository into `~/.codex/plugins/cache` as the primary fix. Use it only as a diagnostic fallback.
|
||||||
|
- Do not leave both `claude-mem@thedotmack` and a new local `claude-mem` enabled if Codex treats them as distinct plugins.
|
||||||
|
- Do not accept "marketplace upgraded" as proof. The cache path and loaded skill path are the source of truth.
|
||||||
|
|
||||||
|
## Phase 2: Installer Fix
|
||||||
|
|
||||||
|
What to implement:
|
||||||
|
|
||||||
|
- Change the Codex installer outcome from "registered marketplace" to "registered marketplace and verified installability".
|
||||||
|
- Add a post-registration diagnostic that checks whether an enabled stale `claude-mem` plugin is already present.
|
||||||
|
- If a stale cache is detected, print a direct remediation message that names the exact stale cache path and exact `/plugins` action required.
|
||||||
|
- If Codex exposes an install/enable CLI in a future version, use it. In `0.128.0`, keep the `/plugins` step but verify and report the gap.
|
||||||
|
|
||||||
|
Code references:
|
||||||
|
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:10` for `MARKETPLACE_NAME`.
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:12` through `src/services/integrations/CodexCliInstaller.ts:16` for required marketplace files.
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts:175` through `src/services/integrations/CodexCliInstaller.ts:214` for install flow.
|
||||||
|
- `src/npx-cli/commands/install.ts:269` through `src/npx-cli/commands/install.ts:282` for task status text.
|
||||||
|
- `tests/install-non-tty.test.ts` for existing installer behavior assertions.
|
||||||
|
|
||||||
|
Suggested implementation details:
|
||||||
|
|
||||||
|
- Add a `diagnoseCodexPluginState()` helper that reads:
|
||||||
|
- `~/.codex/config.toml`
|
||||||
|
- `~/.codex/plugins/cache/**/claude-mem/**/.codex-plugin/plugin.json`
|
||||||
|
- `~/.codex/plugins/cache/**/claude-mem/**/.claude-plugin/plugin.json`
|
||||||
|
- `~/.codex/plugins/cache/**/claude-mem/**/.install-version`
|
||||||
|
|
||||||
|
- Classify state as:
|
||||||
|
- `not_installed`
|
||||||
|
- `installed_current_codex`
|
||||||
|
- `installed_stale_codex`
|
||||||
|
- `installed_legacy_claude_shape`
|
||||||
|
- `duplicate_installs`
|
||||||
|
|
||||||
|
- Include current repo/package version in the expected state.
|
||||||
|
- Treat `installed_legacy_claude_shape` as a warning or failure for Codex integration, because it is the exact observed bad state.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Unit tests cover stale `12.3.1` legacy cache with `.claude-plugin` only.
|
||||||
|
- Unit tests cover current `12.7.2` first-class cache with `.codex-plugin`.
|
||||||
|
- Unit tests cover duplicate stale plus current installs.
|
||||||
|
- Installer output no longer says only "hooks marketplace registered" when the installed plugin cache is stale.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- Do not parse TOML with regex if a TOML parser is already available in the dependency set.
|
||||||
|
- Do not bake in `12.7.2`; read expected version from package metadata.
|
||||||
|
- Do not rely on `~/.codex/.tmp/marketplaces/...` as proof of plugin installation.
|
||||||
|
|
||||||
|
## Phase 3: Repair Command Fix
|
||||||
|
|
||||||
|
What to implement:
|
||||||
|
|
||||||
|
- Extend `npx claude-mem repair --ide codex-cli` or equivalent repair flow to handle Codex first-class plugin state.
|
||||||
|
- The repair should:
|
||||||
|
- Register or upgrade the local marketplace.
|
||||||
|
- Detect stale enabled `claude-mem@thedotmack`.
|
||||||
|
- Tell the user whether manual `/plugins` installation is still required.
|
||||||
|
- Verify the active cache after restart or after the user completes `/plugins`.
|
||||||
|
|
||||||
|
Code references:
|
||||||
|
|
||||||
|
- `src/npx-cli/commands/install.ts` for marketplace copy and IDE task orchestration.
|
||||||
|
- `src/services/integrations/CodexCliInstaller.ts` for Codex-specific registration.
|
||||||
|
- `src/npx-cli/commands/uninstall.ts` for uninstall symmetry.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Repair from a synthetic `12.3.1` legacy cache reports the correct stale-cache diagnosis.
|
||||||
|
- Repair from a current cache is idempotent.
|
||||||
|
- Repair does not remove unrelated Codex settings or non-claude-mem plugins.
|
||||||
|
|
||||||
|
Anti-pattern guards:
|
||||||
|
|
||||||
|
- Do not silently delete old caches without a backup or explicit command mode.
|
||||||
|
- Do not make repair depend on an interactive TUI if the install command supports non-TTY mode.
|
||||||
|
|
||||||
|
## Phase 4: Documentation Fix
|
||||||
|
|
||||||
|
What to update:
|
||||||
|
|
||||||
|
- Document that Codex currently has two steps:
|
||||||
|
- Marketplace registration via `npx claude-mem install`.
|
||||||
|
- Plugin install/enable via `/plugins`.
|
||||||
|
|
||||||
|
- Add troubleshooting for this exact mismatch:
|
||||||
|
- Symptom: Codex skill list shows `~/.codex/plugins/cache/thedotmack/claude-mem/12.3.1`.
|
||||||
|
- Cause: stale installed plugin cache, despite current marketplace source.
|
||||||
|
- Fix: uninstall old `claude-mem@thedotmack`, install from `claude-mem (local)`, restart Codex.
|
||||||
|
|
||||||
|
Code/doc references:
|
||||||
|
|
||||||
|
- `docs/public/installation.mdx`
|
||||||
|
- `docs/public/troubleshooting.mdx`
|
||||||
|
- `README.md`
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
|
||||||
|
- Docs mention the cache path as a diagnostic check.
|
||||||
|
- Docs do not imply that `codex plugin marketplace add` alone installs the plugin.
|
||||||
|
|
||||||
|
## Phase 5: End-To-End Verification
|
||||||
|
|
||||||
|
Manual verification checklist:
|
||||||
|
|
||||||
|
- Fresh install on a clean Codex profile.
|
||||||
|
- Upgrade from old `12.3.1` cache.
|
||||||
|
- Upgrade from current marketplace but stale installed cache.
|
||||||
|
- Duplicate install case with both `claude-mem@thedotmack` and local `claude-mem`.
|
||||||
|
|
||||||
|
Acceptance criteria:
|
||||||
|
|
||||||
|
- Fresh Codex session loads `claude-mem:` skills from a current cache path.
|
||||||
|
- Loaded plugin cache contains `.codex-plugin/plugin.json`.
|
||||||
|
- Loaded plugin cache contains Codex hooks at the path declared by `.codex-plugin/plugin.json`.
|
||||||
|
- MCP server starts through the current `.mcp.json` wrapper.
|
||||||
|
- Installer and repair output make stale-cache state explicit.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Does the Codex `/plugins` UI use marketplace name, repository owner, or plugin author to derive the installed plugin cache namespace?
|
||||||
|
- Does `codex plugin marketplace upgrade claude-mem-local` intentionally avoid updating already-installed plugin caches?
|
||||||
|
- Is there a hidden or upcoming non-interactive plugin install command that can replace the manual `/plugins` step?
|
||||||
|
- Should the installer remove or disable `claude-mem@thedotmack` when installing `claude-mem-local`, or should it only warn?
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
"command": "sh",
|
"command": "sh",
|
||||||
"args": [
|
"args": [
|
||||||
"-c",
|
"-c",
|
||||||
"_C=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"; _E=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; _P=$({ [ -n \"$_E\" ] && printf '%s\\n' \"$_E\"; ls -dt \"$_C/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ 2>/dev/null; printf '%s\\n' \"$_C/plugins/marketplaces/thedotmack/plugin\"; } | while IFS= read -r _R; do _R=\"${_R%/}\"; [ -d \"$_R/plugin/scripts\" ] && _Q=\"$_R/plugin\" || _Q=\"$_R\"; [ -f \"$_Q/scripts/mcp-server.cjs\" ] && { printf '%s\\n' \"$_Q\"; break; }; done); [ -n \"$_P\" ] || { echo \"claude-mem: mcp server not found\" >&2; exit 1; }; exec node \"$_P/scripts/mcp-server.cjs\""
|
"_C=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"; _E=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; _P=$({ [ -n \"$_E\" ] && printf '%s\\n' \"$_E\"; printf '%s\\n' \"$PWD/plugin\" \"$PWD\"; ls -dt \"$HOME/.codex/plugins/cache/claude-mem-local/claude-mem\"/[0-9]*/ \"$HOME/.codex/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ \"$_C/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ 2>/dev/null; printf '%s\\n' \"$_C/plugins/marketplaces/thedotmack/plugin\"; } | while IFS= read -r _R; do _R=\"${_R%/}\"; [ -d \"$_R/plugin/scripts\" ] && _Q=\"$_R/plugin\" || _Q=\"$_R\"; [ -f \"$_Q/scripts/mcp-server.cjs\" ] && { printf '%s\\n' \"$_Q\"; break; }; done); [ -n \"$_P\" ] || { echo \"claude-mem: mcp server not found\" >&2; exit 1; }; exec node \"$_P/scripts/mcp-server.cjs\""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+214
-219
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -384,6 +384,23 @@ async function buildHooks() {
|
|||||||
throw new Error(`plugin/hooks/codex-hooks.json contains unknown Codex hook event: ${eventName}`);
|
throw new Error(`plugin/hooks/codex-hooks.json contains unknown Codex hook event: ${eventName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const codexMarketplace = JSON.parse(fs.readFileSync('.agents/plugins/marketplace.json', 'utf-8'));
|
||||||
|
const claudeMemMarketplaceEntry = (codexMarketplace.plugins ?? []).find((plugin) => plugin.name === 'claude-mem');
|
||||||
|
if (claudeMemMarketplaceEntry?.source?.path !== './plugin') {
|
||||||
|
throw new Error('.agents/plugins/marketplace.json must point claude-mem source.path at ./plugin so Codex loads the bundled plugin root');
|
||||||
|
}
|
||||||
|
const rootMcp = JSON.parse(fs.readFileSync('.mcp.json', 'utf-8'));
|
||||||
|
const bundledMcp = JSON.parse(fs.readFileSync('plugin/.mcp.json', 'utf-8'));
|
||||||
|
if (JSON.stringify(rootMcp.mcpServers?.['mcp-search']) !== JSON.stringify(bundledMcp.mcpServers?.['mcp-search'])) {
|
||||||
|
throw new Error('.mcp.json and plugin/.mcp.json mcp-search launchers must stay in sync');
|
||||||
|
}
|
||||||
|
const mcpSearchCommand = bundledMcp.mcpServers?.['mcp-search']?.args?.join(' ') ?? '';
|
||||||
|
if (!mcpSearchCommand.includes('.codex/plugins/cache/claude-mem-local/claude-mem')) {
|
||||||
|
throw new Error('plugin/.mcp.json mcp-search launcher must include Codex cache fallback for hosts that do not inject PLUGIN_ROOT');
|
||||||
|
}
|
||||||
|
if (!mcpSearchCommand.includes('plugins/cache/thedotmack/claude-mem')) {
|
||||||
|
throw new Error('plugin/.mcp.json mcp-search launcher must include Claude cache fallback for hosts that do not inject PLUGIN_ROOT');
|
||||||
|
}
|
||||||
console.log('✓ All required distribution files present');
|
console.log('✓ All required distribution files present');
|
||||||
|
|
||||||
console.log('\n✅ All build targets compiled successfully!');
|
console.log('\n✅ All build targets compiled successfully!');
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ import { execFileSync, spawnSync } from 'child_process';
|
|||||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { logger } from '../../utils/logger.js';
|
import { logger } from '../../utils/logger.js';
|
||||||
|
import { paths } from '../../shared/paths.js';
|
||||||
|
|
||||||
const CODEX_DIR = path.join(homedir(), '.codex');
|
const CODEX_DIR = path.join(homedir(), '.codex');
|
||||||
const CODEX_AGENTS_MD_PATH = path.join(CODEX_DIR, 'AGENTS.md');
|
const CODEX_AGENTS_MD_PATH = path.join(CODEX_DIR, 'AGENTS.md');
|
||||||
|
const CODEX_TRANSCRIPT_WATCH_CONFIG_PATH = paths.transcriptsConfig();
|
||||||
const MARKETPLACE_NAME = 'claude-mem-local';
|
const MARKETPLACE_NAME = 'claude-mem-local';
|
||||||
const MIN_CODEX_MARKETPLACE_VERSION = '0.128.0';
|
const MIN_CODEX_MARKETPLACE_VERSION = '0.128.0';
|
||||||
const REQUIRED_MARKETPLACE_FILES = [
|
const REQUIRED_MARKETPLACE_FILES = [
|
||||||
path.join('.agents', 'plugins', 'marketplace.json'),
|
path.join('.agents', 'plugins', 'marketplace.json'),
|
||||||
path.join('.codex-plugin', 'plugin.json'),
|
path.join('plugin', '.codex-plugin', 'plugin.json'),
|
||||||
'.mcp.json',
|
path.join('plugin', '.mcp.json'),
|
||||||
|
path.join('plugin', 'hooks', 'codex-hooks.json'),
|
||||||
|
path.join('plugin', 'skills', 'mem-search', 'SKILL.md'),
|
||||||
];
|
];
|
||||||
|
|
||||||
function commandExists(command: string): boolean {
|
function commandExists(command: string): boolean {
|
||||||
@@ -28,10 +32,10 @@ function commandExists(command: string): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findAncestorWithCodexPlugin(start: string): string | null {
|
function findAncestorWithCodexMarketplace(start: string): string | null {
|
||||||
let current = path.resolve(start);
|
let current = path.resolve(start);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (existsSync(path.join(current, '.codex-plugin', 'plugin.json'))) {
|
if (existsSync(path.join(current, '.agents', 'plugins', 'marketplace.json'))) {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
const parent = path.dirname(current);
|
const parent = path.dirname(current);
|
||||||
@@ -66,11 +70,11 @@ function resolvePluginMarketplaceRoot(preferredRoot?: string): string {
|
|||||||
].filter((value): value is string => Boolean(value));
|
].filter((value): value is string => Boolean(value));
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
const resolved = findAncestorWithCodexPlugin(candidate);
|
const resolved = findAncestorWithCodexMarketplace(candidate);
|
||||||
if (resolved && missingMarketplaceFiles(resolved).length === 0) return resolved;
|
if (resolved && missingMarketplaceFiles(resolved).length === 0) return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Could not locate a Codex marketplace root with .agents/plugins/marketplace.json, .codex-plugin/plugin.json, and .mcp.json. Run npx claude-mem@latest install from the package or repo root.');
|
throw new Error('Could not locate a Codex marketplace root with .agents/plugins/marketplace.json and plugin/.codex-plugin/plugin.json. Run npx claude-mem@latest install from the package or repo root.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function runCodex(args: string[]): void {
|
function runCodex(args: string[]): void {
|
||||||
@@ -94,6 +98,18 @@ function runCodex(args: string[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function runCodexBestEffort(args: string[], successMessage: string, failureMessage: string): boolean {
|
||||||
|
try {
|
||||||
|
runCodex(args);
|
||||||
|
console.log(` ${successMessage}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
console.warn(` ${failureMessage}: ${message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseSemver(value: string): [number, number, number] | null {
|
function parseSemver(value: string): [number, number, number] | null {
|
||||||
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
const match = value.match(/(\d+)\.(\d+)\.(\d+)/);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
@@ -172,6 +188,66 @@ function readAndStripContextTags(startTag: string, endTag: string): void {
|
|||||||
|
|
||||||
const cleanupLegacyCodexAgentsMdContext = removeCodexAgentsMdContext;
|
const cleanupLegacyCodexAgentsMdContext = removeCodexAgentsMdContext;
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCodexTranscriptWatch(watch: Record<string, unknown>): boolean {
|
||||||
|
return watch.name === 'codex' || watch.schema === 'codex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandHome(inputPath: string): string {
|
||||||
|
if (inputPath === '~') return homedir();
|
||||||
|
if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
|
||||||
|
return path.join(homedir(), inputPath.slice(2));
|
||||||
|
}
|
||||||
|
return inputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLegacyCodexAgentsContext(context: Record<string, unknown>): boolean {
|
||||||
|
if (context.mode !== 'agents') return false;
|
||||||
|
|
||||||
|
const updateOn = context.updateOn;
|
||||||
|
const hasLegacyUpdateOn = Array.isArray(updateOn)
|
||||||
|
&& updateOn.length === 2
|
||||||
|
&& updateOn.includes('session_start')
|
||||||
|
&& updateOn.includes('session_end');
|
||||||
|
if (!hasLegacyUpdateOn) return false;
|
||||||
|
|
||||||
|
if (context.path === undefined) return true;
|
||||||
|
return typeof context.path === 'string'
|
||||||
|
&& path.resolve(expandHome(context.path)) === CODEX_AGENTS_MD_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disableCodexTranscriptAgentsContext(): boolean {
|
||||||
|
if (!existsSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH)) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(readFileSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH, 'utf-8')) as unknown;
|
||||||
|
if (!isRecord(parsed) || !Array.isArray(parsed.watches)) return true;
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
for (const watch of parsed.watches) {
|
||||||
|
if (!isRecord(watch) || !isCodexTranscriptWatch(watch)) continue;
|
||||||
|
if (!isRecord(watch.context) || !isLegacyCodexAgentsContext(watch.context)) continue;
|
||||||
|
delete watch.context;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
writeFileSync(CODEX_TRANSCRIPT_WATCH_CONFIG_PATH, `${JSON.stringify(parsed, null, 2)}\n`);
|
||||||
|
console.log(` Disabled legacy Codex transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
logger.warn('WORKER', 'Failed to disable Codex transcript AGENTS.md context', { error: message });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupLegacyCodexTranscriptAgentsContext = disableCodexTranscriptAgentsContext;
|
||||||
|
|
||||||
export async function installCodexCli(marketplaceRootOverride?: string): Promise<number> {
|
export async function installCodexCli(marketplaceRootOverride?: string): Promise<number> {
|
||||||
console.log('\nInstalling Claude-Mem for Codex CLI (native hooks)...\n');
|
console.log('\nInstalling Claude-Mem for Codex CLI (native hooks)...\n');
|
||||||
|
|
||||||
@@ -187,9 +263,22 @@ export async function installCodexCli(marketplaceRootOverride?: string): Promise
|
|||||||
|
|
||||||
console.log(` Registering Codex plugin marketplace: ${marketplaceRoot}`);
|
console.log(` Registering Codex plugin marketplace: ${marketplaceRoot}`);
|
||||||
runCodex(['plugin', 'marketplace', 'add', marketplaceRoot]);
|
runCodex(['plugin', 'marketplace', 'add', marketplaceRoot]);
|
||||||
|
runCodexBestEffort(
|
||||||
|
['plugin', 'marketplace', 'upgrade', MARKETPLACE_NAME],
|
||||||
|
'Refreshed Codex marketplace and installed plugin cache.',
|
||||||
|
'Could not refresh Codex marketplace cache; reinstall or upgrade claude-mem from /plugins if Codex still uses old MCP config',
|
||||||
|
);
|
||||||
|
runCodexBestEffort(
|
||||||
|
['features', 'enable', 'plugin_hooks'],
|
||||||
|
'Enabled Codex plugin_hooks so claude-mem hooks can run.',
|
||||||
|
'Could not enable Codex plugin_hooks; run `codex features enable plugin_hooks` if context hooks do not appear',
|
||||||
|
);
|
||||||
if (!cleanupLegacyCodexAgentsMdContext()) {
|
if (!cleanupLegacyCodexAgentsMdContext()) {
|
||||||
console.warn(` Native Codex hooks registered, but failed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`);
|
console.warn(` Native Codex hooks registered, but failed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`);
|
||||||
}
|
}
|
||||||
|
if (!cleanupLegacyCodexTranscriptAgentsContext()) {
|
||||||
|
console.warn(` Native Codex hooks registered, but failed to disable legacy transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}.`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
Installation complete!
|
Installation complete!
|
||||||
@@ -201,6 +290,7 @@ Next steps:
|
|||||||
1. Open Codex CLI in your project
|
1. Open Codex CLI in your project
|
||||||
2. Run /plugins
|
2. Run /plugins
|
||||||
3. Install claude-mem from the claude-mem (local) marketplace
|
3. Install claude-mem from the claude-mem (local) marketplace
|
||||||
|
4. Restart Codex CLI after install so MCP tools and plugin hooks reload
|
||||||
|
|
||||||
For a fresh setup, the supported entry point is:
|
For a fresh setup, the supported entry point is:
|
||||||
npx claude-mem@latest install
|
npx claude-mem@latest install
|
||||||
@@ -235,9 +325,13 @@ export function uninstallCodexCli(): number {
|
|||||||
console.error(`\nFailed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`);
|
console.error(`\nFailed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`);
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
|
if (!cleanupLegacyCodexTranscriptAgentsContext()) {
|
||||||
|
console.error(`\nFailed to disable legacy transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}.`);
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
console.error(`\nLegacy AGENTS.md cleanup failed: ${message}`);
|
console.error(`\nLegacy context cleanup failed: ${message}`);
|
||||||
failed = true;
|
failed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,15 +47,14 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tool-use',
|
name: 'tool-use',
|
||||||
match: { path: 'payload.type', in: ['function_call', 'custom_tool_call', 'web_search_call', 'exec_command'] },
|
match: { path: 'payload.type', in: ['function_call', 'custom_tool_call', 'web_search_call'] },
|
||||||
action: 'tool_use',
|
action: 'tool_use',
|
||||||
fields: {
|
fields: {
|
||||||
toolId: 'payload.call_id',
|
toolId: 'payload.call_id',
|
||||||
toolName: {
|
toolName: {
|
||||||
coalesce: [
|
coalesce: [
|
||||||
'payload.name',
|
'payload.name',
|
||||||
'payload.type',
|
'payload.type'
|
||||||
{ value: 'web_search' }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
toolInput: {
|
toolInput: {
|
||||||
@@ -70,16 +69,38 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tool-result',
|
name: 'tool-result',
|
||||||
match: { path: 'payload.type', in: ['function_call_output', 'custom_tool_call_output', 'exec_command_output'] },
|
match: { path: 'payload.type', in: ['function_call_output', 'custom_tool_call_output'] },
|
||||||
action: 'tool_result',
|
action: 'tool_result',
|
||||||
fields: {
|
fields: {
|
||||||
toolId: 'payload.call_id',
|
toolId: 'payload.call_id',
|
||||||
toolResponse: 'payload.output'
|
toolResponse: 'payload.output'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'exec-command-end',
|
||||||
|
match: { path: 'payload.type', in: ['exec_command_end', 'exec_command_output'] },
|
||||||
|
action: 'observation',
|
||||||
|
fields: {
|
||||||
|
toolUseId: 'payload.call_id',
|
||||||
|
toolName: { value: 'exec_command' },
|
||||||
|
toolInput: {
|
||||||
|
coalesce: [
|
||||||
|
'payload.command',
|
||||||
|
'payload.input'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
toolResponse: {
|
||||||
|
coalesce: [
|
||||||
|
'payload.aggregated_output',
|
||||||
|
'payload.output',
|
||||||
|
'payload.stdout',
|
||||||
|
'payload.stderr'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'session-end',
|
name: 'session-end',
|
||||||
// TODO(#2249): delete watcher when Codex hook lifecycle migration ships
|
|
||||||
match: { path: 'payload.type', in: ['turn_aborted', 'turn_completed', 'task_complete'] },
|
match: { path: 'payload.type', in: ['turn_aborted', 'turn_completed', 'task_complete'] },
|
||||||
action: 'session_end'
|
action: 'session_end'
|
||||||
}
|
}
|
||||||
@@ -96,11 +117,7 @@ export const SAMPLE_CONFIG: TranscriptWatchConfig = {
|
|||||||
name: 'codex',
|
name: 'codex',
|
||||||
path: '~/.codex/sessions/**/*.jsonl',
|
path: '~/.codex/sessions/**/*.jsonl',
|
||||||
schema: 'codex',
|
schema: 'codex',
|
||||||
startAtEnd: true,
|
startAtEnd: true
|
||||||
context: {
|
|
||||||
mode: 'agents',
|
|
||||||
updateOn: ['session_start', 'session_end']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
stateFile: DEFAULT_STATE_PATH
|
stateFile: DEFAULT_STATE_PATH
|
||||||
|
|||||||
@@ -57,8 +57,12 @@ describe('Plugin Distribution - Skills', () => {
|
|||||||
describe('Plugin Distribution - Required Files', () => {
|
describe('Plugin Distribution - Required Files', () => {
|
||||||
const requiredFiles = [
|
const requiredFiles = [
|
||||||
'plugin/hooks/hooks.json',
|
'plugin/hooks/hooks.json',
|
||||||
|
'plugin/hooks/codex-hooks.json',
|
||||||
'plugin/.claude-plugin/plugin.json',
|
'plugin/.claude-plugin/plugin.json',
|
||||||
|
'plugin/.codex-plugin/plugin.json',
|
||||||
|
'plugin/.mcp.json',
|
||||||
'plugin/skills/mem-search/SKILL.md',
|
'plugin/skills/mem-search/SKILL.md',
|
||||||
|
'.agents/plugins/marketplace.json',
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const filePath of requiredFiles) {
|
for (const filePath of requiredFiles) {
|
||||||
@@ -69,6 +73,32 @@ describe('Plugin Distribution - Required Files', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Plugin Distribution - Codex Marketplace', () => {
|
||||||
|
it('points Codex at the bundled plugin root', () => {
|
||||||
|
const marketplacePath = path.join(projectRoot, '.agents/plugins/marketplace.json');
|
||||||
|
const marketplace = JSON.parse(readFileSync(marketplacePath, 'utf-8'));
|
||||||
|
|
||||||
|
expect(marketplace.plugins[0].source.path).toBe('./plugin');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MCP launcher can recover without plugin root environment variables', () => {
|
||||||
|
const mcpPath = path.join(projectRoot, 'plugin/.mcp.json');
|
||||||
|
const mcp = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
||||||
|
const command = mcp.mcpServers['mcp-search'].args.join(' ');
|
||||||
|
|
||||||
|
expect(command).toContain('.codex/plugins/cache/claude-mem-local/claude-mem');
|
||||||
|
expect(command).toContain('plugins/cache/thedotmack/claude-mem');
|
||||||
|
expect(command).toContain('claude-mem: mcp server not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps root and bundled MCP launchers in sync', () => {
|
||||||
|
const rootMcp = JSON.parse(readFileSync(path.join(projectRoot, '.mcp.json'), 'utf-8'));
|
||||||
|
const bundledMcp = JSON.parse(readFileSync(path.join(projectRoot, 'plugin/.mcp.json'), 'utf-8'));
|
||||||
|
|
||||||
|
expect(rootMcp.mcpServers['mcp-search']).toEqual(bundledMcp.mcpServers['mcp-search']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Plugin Distribution - hooks.json Integrity', () => {
|
describe('Plugin Distribution - hooks.json Integrity', () => {
|
||||||
it('should have valid JSON in hooks.json', () => {
|
it('should have valid JSON in hooks.json', () => {
|
||||||
const hooksPath = path.join(projectRoot, 'plugin/hooks/hooks.json');
|
const hooksPath = path.join(projectRoot, 'plugin/hooks/hooks.json');
|
||||||
@@ -148,13 +178,15 @@ describe('Plugin Distribution - Startup Root Resolution', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Plugin Distribution - package.json Files Field', () => {
|
describe('Plugin Distribution - package.json Files Field', () => {
|
||||||
it('should include plugin distribution files in root package.json files field', () => {
|
it('should include bundled plugin entries in root package.json files field', () => {
|
||||||
const packageJsonPath = path.join(projectRoot, 'package.json');
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
||||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||||
expect(packageJson.files).toBeDefined();
|
expect(packageJson.files).toBeDefined();
|
||||||
expect(packageJson.files).toContain('plugin/hooks');
|
expect(packageJson.files).toContain('plugin/.codex-plugin');
|
||||||
expect(packageJson.files).toContain('plugin/.mcp.json');
|
expect(packageJson.files).toContain('plugin/.mcp.json');
|
||||||
|
expect(packageJson.files).toContain('plugin/hooks');
|
||||||
expect(packageJson.files).toContain('plugin/skills');
|
expect(packageJson.files).toContain('plugin/skills');
|
||||||
|
expect(packageJson.files).toContain('plugin/scripts/*.cjs');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ const syncMarketplaceSourcePath = join(
|
|||||||
'sync-marketplace.cjs',
|
'sync-marketplace.cjs',
|
||||||
);
|
);
|
||||||
const syncMarketplaceSource = readFileSync(syncMarketplaceSourcePath, 'utf-8');
|
const syncMarketplaceSource = readFileSync(syncMarketplaceSourcePath, 'utf-8');
|
||||||
|
const transcriptConfigSourcePath = join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'src',
|
||||||
|
'services',
|
||||||
|
'transcripts',
|
||||||
|
'config.ts',
|
||||||
|
);
|
||||||
|
const transcriptConfigSource = readFileSync(transcriptConfigSourcePath, 'utf-8');
|
||||||
|
|
||||||
describe('Install Non-TTY Support', () => {
|
describe('Install Non-TTY Support', () => {
|
||||||
describe('isInteractive flag', () => {
|
describe('isInteractive flag', () => {
|
||||||
@@ -103,6 +112,13 @@ describe('Install Non-TTY Support', () => {
|
|||||||
expect(copyRegion).toContain("'.mcp.json'");
|
expect(copyRegion).toContain("'.mcp.json'");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('validates the bundled plugin as the Codex marketplace source', () => {
|
||||||
|
expect(codexInstallerSource).toContain("path.join('plugin', '.codex-plugin', 'plugin.json')");
|
||||||
|
expect(codexInstallerSource).toContain("path.join('plugin', '.mcp.json')");
|
||||||
|
expect(codexInstallerSource).toContain("path.join('plugin', 'hooks', 'codex-hooks.json')");
|
||||||
|
expect(codexInstallerSource).toContain("path.join('plugin', 'skills', 'mem-search', 'SKILL.md')");
|
||||||
|
});
|
||||||
|
|
||||||
it('does not exclude MCP manifests during local marketplace sync', () => {
|
it('does not exclude MCP manifests during local marketplace sync', () => {
|
||||||
const gitignoreExcludeRegion = syncMarketplaceSource.slice(
|
const gitignoreExcludeRegion = syncMarketplaceSource.slice(
|
||||||
syncMarketplaceSource.indexOf('function getGitignoreExcludes'),
|
syncMarketplaceSource.indexOf('function getGitignoreExcludes'),
|
||||||
@@ -116,6 +132,24 @@ describe('Install Non-TTY Support', () => {
|
|||||||
expect(installSource).toContain('installCodexCli(marketplaceDirectory())');
|
expect(installSource).toContain('installCodexCli(marketplaceDirectory())');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('refreshes Codex marketplace cache after registration', () => {
|
||||||
|
const installRegion = codexInstallerSource.slice(
|
||||||
|
codexInstallerSource.indexOf('export async function installCodexCli'),
|
||||||
|
codexInstallerSource.indexOf('export function uninstallCodexCli'),
|
||||||
|
);
|
||||||
|
expect(installRegion).toContain("['plugin', 'marketplace', 'upgrade', MARKETPLACE_NAME]");
|
||||||
|
expect(installRegion).toContain('installed plugin cache');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables Codex plugin hooks during install', () => {
|
||||||
|
const installRegion = codexInstallerSource.slice(
|
||||||
|
codexInstallerSource.indexOf('export async function installCodexCli'),
|
||||||
|
codexInstallerSource.indexOf('export function uninstallCodexCli'),
|
||||||
|
);
|
||||||
|
expect(installRegion).toContain("['features', 'enable', 'plugin_hooks']");
|
||||||
|
expect(installRegion).toContain('codex features enable plugin_hooks');
|
||||||
|
});
|
||||||
|
|
||||||
it('captures Codex CLI output for install failure reporting', () => {
|
it('captures Codex CLI output for install failure reporting', () => {
|
||||||
const runCodexRegion = codexInstallerSource.slice(
|
const runCodexRegion = codexInstallerSource.slice(
|
||||||
codexInstallerSource.indexOf('function runCodex'),
|
codexInstallerSource.indexOf('function runCodex'),
|
||||||
@@ -147,7 +181,9 @@ describe('Install Non-TTY Support', () => {
|
|||||||
|
|
||||||
it('reports legacy Codex AGENTS cleanup failures to callers', () => {
|
it('reports legacy Codex AGENTS cleanup failures to callers', () => {
|
||||||
expect(codexInstallerSource).toContain('function removeCodexAgentsMdContext(): boolean');
|
expect(codexInstallerSource).toContain('function removeCodexAgentsMdContext(): boolean');
|
||||||
|
expect(codexInstallerSource).toContain('function disableCodexTranscriptAgentsContext(): boolean');
|
||||||
expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexAgentsMdContext())');
|
expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexAgentsMdContext())');
|
||||||
|
expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexTranscriptAgentsContext())');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not fail Codex install after marketplace registration when only AGENTS cleanup fails', () => {
|
it('does not fail Codex install after marketplace registration when only AGENTS cleanup fails', () => {
|
||||||
@@ -162,6 +198,17 @@ describe('Install Non-TTY Support', () => {
|
|||||||
expect(cleanupFailureRegion).toContain('console.warn');
|
expect(cleanupFailureRegion).toContain('console.warn');
|
||||||
expect(cleanupFailureRegion).not.toContain('return 1');
|
expect(cleanupFailureRegion).not.toContain('return 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not seed new Codex transcript watcher configs with AGENTS context injection', () => {
|
||||||
|
expect(transcriptConfigSource).toContain("name: 'codex'");
|
||||||
|
const codexWatchRegion = transcriptConfigSource.slice(
|
||||||
|
transcriptConfigSource.indexOf("name: 'codex'"),
|
||||||
|
transcriptConfigSource.indexOf('stateFile: DEFAULT_STATE_PATH'),
|
||||||
|
);
|
||||||
|
expect(codexWatchRegion).toContain("path: '~/.codex/sessions/**/*.jsonl'");
|
||||||
|
expect(codexWatchRegion).not.toContain("mode: 'agents'");
|
||||||
|
expect(codexWatchRegion).not.toContain('updateOn');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('TaskDescriptor interface', () => {
|
describe('TaskDescriptor interface', () => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"schemas": {
|
"schemas": {
|
||||||
"codex": {
|
"codex": {
|
||||||
"name": "codex",
|
"name": "codex",
|
||||||
"version": "0.2",
|
"version": "0.3",
|
||||||
"description": "Schema for Codex session JSONL files under ~/.codex/sessions.",
|
"description": "Schema for Codex session JSONL files under ~/.codex/sessions.",
|
||||||
"events": [
|
"events": [
|
||||||
{
|
{
|
||||||
@@ -48,18 +48,42 @@
|
|||||||
"toolName": {
|
"toolName": {
|
||||||
"coalesce": [
|
"coalesce": [
|
||||||
"payload.name",
|
"payload.name",
|
||||||
{ "value": "web_search" }
|
"payload.type"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"toolInput": {
|
"toolInput": {
|
||||||
"coalesce": [
|
"coalesce": [
|
||||||
"payload.arguments",
|
"payload.arguments",
|
||||||
"payload.input",
|
"payload.input",
|
||||||
|
"payload.command",
|
||||||
"payload.action"
|
"payload.action"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "exec-command-end",
|
||||||
|
"match": { "path": "payload.type", "in": ["exec_command_end", "exec_command_output"] },
|
||||||
|
"action": "observation",
|
||||||
|
"fields": {
|
||||||
|
"toolUseId": "payload.call_id",
|
||||||
|
"toolName": { "value": "exec_command" },
|
||||||
|
"toolInput": {
|
||||||
|
"coalesce": [
|
||||||
|
"payload.command",
|
||||||
|
"payload.input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"toolResponse": {
|
||||||
|
"coalesce": [
|
||||||
|
"payload.aggregated_output",
|
||||||
|
"payload.output",
|
||||||
|
"payload.stdout",
|
||||||
|
"payload.stderr"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "tool-result",
|
"name": "tool-result",
|
||||||
"match": { "path": "payload.type", "in": ["function_call_output", "custom_tool_call_output"] },
|
"match": { "path": "payload.type", "in": ["function_call_output", "custom_tool_call_output"] },
|
||||||
@@ -71,7 +95,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "session-end",
|
"name": "session-end",
|
||||||
"match": { "path": "payload.type", "equals": "turn_aborted" },
|
"match": { "path": "payload.type", "in": ["turn_aborted", "turn_completed", "task_complete"] },
|
||||||
"action": "session_end"
|
"action": "session_end"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -82,12 +106,7 @@
|
|||||||
"name": "codex",
|
"name": "codex",
|
||||||
"path": "~/.codex/sessions/**/*.jsonl",
|
"path": "~/.codex/sessions/**/*.jsonl",
|
||||||
"schema": "codex",
|
"schema": "codex",
|
||||||
"startAtEnd": true,
|
"startAtEnd": true
|
||||||
"context": {
|
|
||||||
"mode": "agents",
|
|
||||||
"path": "~/.codex/AGENTS.md",
|
|
||||||
"updateOn": ["session_start", "session_end"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stateFile": "~/.claude-mem/transcript-watch-state.json"
|
"stateFile": "~/.claude-mem/transcript-watch-state.json"
|
||||||
|
|||||||
Reference in New Issue
Block a user