Merge remote-tracking branch 'origin/codex-mode-session-start-hook-migration' into fix-and-ship-codex-mem-search-access

# Conflicts:
#	plugin/scripts/worker-service.cjs
This commit is contained in:
Alex Newman
2026-05-06 19:11:41 -07:00
5 changed files with 149 additions and 22 deletions
File diff suppressed because one or more lines are too long
+70 -1
View File
@@ -4,9 +4,11 @@ import { execFileSync, spawnSync } from 'child_process';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { logger } from '../../utils/logger.js';
import { paths } from '../../shared/paths.js';
const CODEX_DIR = path.join(homedir(), '.codex');
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 MIN_CODEX_MARKETPLACE_VERSION = '0.128.0';
const REQUIRED_MARKETPLACE_FILES = [
@@ -186,6 +188,66 @@ function readAndStripContextTags(startTag: string, endTag: string): void {
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> {
console.log('\nInstalling Claude-Mem for Codex CLI (native hooks)...\n');
@@ -214,6 +276,9 @@ export async function installCodexCli(marketplaceRootOverride?: string): Promise
if (!cleanupLegacyCodexAgentsMdContext()) {
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(`
Installation complete!
@@ -260,9 +325,13 @@ export function uninstallCodexCli(): number {
console.error(`\nFailed to remove legacy AGENTS.md context from ${CODEX_AGENTS_MD_PATH}.`);
failed = true;
}
if (!cleanupLegacyCodexTranscriptAgentsContext()) {
console.error(`\nFailed to disable legacy transcript AGENTS.md context in ${CODEX_TRANSCRIPT_WATCH_CONFIG_PATH}.`);
failed = true;
}
} catch (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;
}
+27 -10
View File
@@ -47,15 +47,14 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = {
},
{
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',
fields: {
toolId: 'payload.call_id',
toolName: {
coalesce: [
'payload.name',
'payload.type',
{ value: 'web_search' }
'payload.type'
]
},
toolInput: {
@@ -70,16 +69,38 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = {
},
{
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',
fields: {
toolId: 'payload.call_id',
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',
// TODO(#2249): delete watcher when Codex hook lifecycle migration ships
match: { path: 'payload.type', in: ['turn_aborted', 'turn_completed', 'task_complete'] },
action: 'session_end'
}
@@ -96,11 +117,7 @@ export const SAMPLE_CONFIG: TranscriptWatchConfig = {
name: 'codex',
path: '~/.codex/sessions/**/*.jsonl',
schema: 'codex',
startAtEnd: true,
context: {
mode: 'agents',
updateOn: ['session_start', 'session_end']
}
startAtEnd: true
}
],
stateFile: DEFAULT_STATE_PATH
+22
View File
@@ -27,6 +27,15 @@ const syncMarketplaceSourcePath = join(
'sync-marketplace.cjs',
);
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('isInteractive flag', () => {
@@ -172,7 +181,9 @@ describe('Install Non-TTY Support', () => {
it('reports legacy Codex AGENTS cleanup failures to callers', () => {
expect(codexInstallerSource).toContain('function removeCodexAgentsMdContext(): boolean');
expect(codexInstallerSource).toContain('function disableCodexTranscriptAgentsContext(): boolean');
expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexAgentsMdContext())');
expect(codexInstallerSource).toContain('if (!cleanupLegacyCodexTranscriptAgentsContext())');
});
it('does not fail Codex install after marketplace registration when only AGENTS cleanup fails', () => {
@@ -187,6 +198,17 @@ describe('Install Non-TTY Support', () => {
expect(cleanupFailureRegion).toContain('console.warn');
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', () => {
+28 -9
View File
@@ -3,7 +3,7 @@
"schemas": {
"codex": {
"name": "codex",
"version": "0.2",
"version": "0.3",
"description": "Schema for Codex session JSONL files under ~/.codex/sessions.",
"events": [
{
@@ -48,18 +48,42 @@
"toolName": {
"coalesce": [
"payload.name",
{ "value": "web_search" }
"payload.type"
]
},
"toolInput": {
"coalesce": [
"payload.arguments",
"payload.input",
"payload.command",
"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",
"match": { "path": "payload.type", "in": ["function_call_output", "custom_tool_call_output"] },
@@ -71,7 +95,7 @@
},
{
"name": "session-end",
"match": { "path": "payload.type", "equals": "turn_aborted" },
"match": { "path": "payload.type", "in": ["turn_aborted", "turn_completed", "task_complete"] },
"action": "session_end"
}
]
@@ -82,12 +106,7 @@
"name": "codex",
"path": "~/.codex/sessions/**/*.jsonl",
"schema": "codex",
"startAtEnd": true,
"context": {
"mode": "agents",
"path": "~/.codex/AGENTS.md",
"updateOn": ["session_start", "session_end"]
}
"startAtEnd": true
}
],
"stateFile": "~/.claude-mem/transcript-watch-state.json"