fix: migrate codex context off agents injection
This commit is contained in:
@@ -4,9 +4,11 @@ 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 = [
|
||||||
@@ -172,6 +174,46 @@ 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 {
|
||||||
|
if (watch.name === 'codex' || watch.schema === 'codex') return true;
|
||||||
|
return typeof watch.path === 'string'
|
||||||
|
&& watch.path.includes('.codex')
|
||||||
|
&& watch.path.includes('sessions');
|
||||||
|
}
|
||||||
|
|
||||||
|
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) || watch.context.mode !== 'agents') 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');
|
||||||
|
|
||||||
@@ -190,6 +232,9 @@ export async function installCodexCli(marketplaceRootOverride?: string): Promise
|
|||||||
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!
|
||||||
@@ -235,6 +280,10 @@ 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 AGENTS.md cleanup failed: ${message}`);
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ const CODEX_SAMPLE_SCHEMA: TranscriptSchema = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
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 +95,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
|
||||||
|
|||||||
@@ -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', () => {
|
||||||
@@ -147,7 +156,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 +173,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": [
|
||||||
{
|
{
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tool-use",
|
"name": "tool-use",
|
||||||
"match": { "path": "payload.type", "in": ["function_call", "custom_tool_call", "web_search_call"] },
|
"match": { "path": "payload.type", "in": ["function_call", "custom_tool_call", "web_search_call", "exec_command"] },
|
||||||
"action": "tool_use",
|
"action": "tool_use",
|
||||||
"fields": {
|
"fields": {
|
||||||
"toolId": "payload.call_id",
|
"toolId": "payload.call_id",
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
"coalesce": [
|
"coalesce": [
|
||||||
"payload.arguments",
|
"payload.arguments",
|
||||||
"payload.input",
|
"payload.input",
|
||||||
|
"payload.command",
|
||||||
"payload.action"
|
"payload.action"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"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", "exec_command_output"] },
|
||||||
"action": "tool_result",
|
"action": "tool_result",
|
||||||
"fields": {
|
"fields": {
|
||||||
"toolId": "payload.call_id",
|
"toolId": "payload.call_id",
|
||||||
@@ -71,7 +72,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 +83,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