feat(context): integrate verified isolated Claude generations (#3452)

This commit is contained in:
haelyra
2026-10-10 17:42:10 -04:00
9 changed files with 477 additions and 83 deletions
+4 -3
View File
@@ -119,8 +119,9 @@ function execute(options) {
stateRoot: options['state-root'], nativeRoot: options['native-root'] }) : null;
if (native && !native.ready) throw new Error('Prepare or recover the native generation before launching');
return { launch: require('./context-profile-launch').launchTaskContext({ ...launchInput, dryRun: options.dryRun,
nativeEnvironment: native ? { home: native.home, codexHome: native.codexHome,
codexPath: native.codexPath, executableDigest: native.executableDigest } : null,
nativeEnvironment: native ? { home: native.home, executableDigest: native.executableDigest,
...(native.target === 'claude' ? { claudeConfigDir: native.claudeConfigDir, claudePath: native.claudePath, pluginDir: native.pluginDir }
: { codexHome: native.codexHome, codexPath: native.codexPath }) } : null,
assertCurrent() {
if (stored) {
const current = require('./context-profile-store').getStoreStatus({ stateRoot: options['state-root'] });
@@ -162,7 +163,7 @@ function run(argv) {
const options = parse(argv);
const value = execute(options);
return { schemaVersion: 'ecc.profile-operation.v1', status: (value.launch?.status === 'failed' || value.interactive?.status === 'failed') ? 'error' : 'success',
summary: options.command === 'start' ? 'Opt-in interactive Codex uses the verified isolated generation and inherited terminal. Context selection remains advisory.'
summary: options.command === 'start' ? 'Opt-in interactive session uses the verified isolated generation and inherited terminal. Context selection remains advisory.'
: options.command === 'run' ? 'Task launch uses selected context and the provider configuration. Inspect the launch result.'
: options.command === 'resolve' ? 'Task context resolved within the selected profile.'
: 'Managed profile generation inspected. Native activation is a separate provider boundary.',
+6 -5
View File
@@ -80,18 +80,19 @@ function startInteractiveProfile({ stateRoot, nativeRoot, dryRun = false } = {},
if (carrier.carrierDigest !== stored.carrierDigest) throw new Error('Stored profile source is stale; set and prepare the current generation before starting');
const current = native.getNativeProfileStatus(input);
if (!current.ready || current.revision !== prepared.revision) throw new Error('Native generation changed before interactive launch');
const env = { PATH: process.env.PATH, HOME: current.home, USERPROFILE: current.home,
CODEX_HOME: current.codexHome, LANG: 'C.UTF-8' };
const claude = current.target === 'claude';
const env = { PATH: process.env.PATH, HOME: current.home, USERPROFILE: current.home, LANG: 'C.UTF-8',
...(claude ? { CLAUDE_CONFIG_DIR: current.claudeConfigDir, ...native.CLAUDE_ISOLATION_ENV } : { CODEX_HOME: current.codexHome }) };
// Terminal capabilities are needed by the TUI; credentials and provider overrides are not inherited.
for (const key of ['TERM', 'COLORTERM', 'TERM_PROGRAM', 'SystemRoot']) {
if (process.env[key]) env[key] = process.env[key];
}
const bootstrapDigest = io.hash(io.read(path.join(current.codexHome, 'AGENTS.md')));
const result = (dependencies.execute || spawnSync)(current.codexPath, [], {
const bootstrapDigest = io.hash(io.read(claude ? path.join(current.claudeConfigDir, 'CLAUDE.md') : path.join(current.codexHome, 'AGENTS.md')));
const result = (dependencies.execute || spawnSync)(current.executable, claude ? ['--plugin-dir', current.pluginDir] : [], {
cwd: process.cwd(), env, shell: false, stdio: 'inherit' });
return { schemaVersion: 'ecc.interactive-profile.v1', status: result.error || result.status !== 0 ? 'failed' : 'exited',
launched: !result.error, exitCode: result.status ?? null, signal: result.signal || null,
...(result.error ? { error: 'Native interactive Codex could not be started' } : {}),
...(result.error ? { error: `Native interactive ${claude ? 'Claude' : 'Codex'} could not be started` } : {}),
nativeRevision: current.revision, providerVersion: current.providerVersion,
bootstrapDigest,
credentialsCopied: false, taskSuccess: 'unverified', enforcement: 'prompt-advisory' };
+8 -6
View File
@@ -9,6 +9,7 @@ function isolatedEnvironment(nativeEnvironment) {
USERPROFILE: nativeEnvironment.home,
...(nativeEnvironment.codexHome ? { CODEX_HOME: nativeEnvironment.codexHome } : {}),
...(nativeEnvironment.claudeConfigDir ? { CLAUDE_CONFIG_DIR: nativeEnvironment.claudeConfigDir } : {}),
...(nativeEnvironment.pluginDir ? require('./context-profile-native').CLAUDE_ISOLATION_ENV : {}),
TMPDIR: nativeEnvironment.home, LANG: 'C.UTF-8' };
if (process.platform === 'win32' && process.env.SystemRoot) env.SystemRoot = process.env.SystemRoot;
return env;
@@ -22,11 +23,11 @@ function launchTaskContext({ task, target = 'codex', dryRun = false, execute = s
if (!Object.hasOwn(adapters, target)) throw new Error(`Unsupported task launcher target: ${target}`);
if (!task || typeof task.query !== 'string' || !task.query.trim()) throw new Error('Task launch requires a non-empty query');
if (nativeEnvironment) {
const launchKeys = target === 'claude'
? { directory: nativeEnvironment.claudeConfigDir, executable: nativeEnvironment.claudePath }
: { directory: nativeEnvironment.codexHome, executable: nativeEnvironment.codexPath };
if (!path.isAbsolute(nativeEnvironment.home || '') || !path.isAbsolute(launchKeys.directory || '')
|| !path.isAbsolute(launchKeys.executable || '')
const launchPaths = target === 'claude'
? [nativeEnvironment.claudeConfigDir, nativeEnvironment.claudePath,
...(nativeEnvironment.pluginDir === undefined ? [] : [nativeEnvironment.pluginDir])]
: [nativeEnvironment.codexHome, nativeEnvironment.codexPath];
if (![nativeEnvironment.home, ...launchPaths].every(value => path.isAbsolute(value || ''))
|| !/^[a-f0-9]{64}$/.test(nativeEnvironment.executableDigest || '')) throw new Error('Invalid isolated native launch environment');
}
let selection = bare
@@ -34,7 +35,8 @@ function launchTaskContext({ task, target = 'codex', dryRun = false, execute = s
selectionMode: 'manual', reason: 'bare-baseline', receipt: { bindingDigest: 'bare' } }
: resolveTaskContext({ ...selectionOptions, task, target, load: !dryRun });
const adapter = { ...adapters[target],
...(nativeEnvironment ? { command: nativeEnvironment.codexPath || nativeEnvironment.claudePath } : {}) };
...(nativeEnvironment ? { command: target === 'claude' ? nativeEnvironment.claudePath : nativeEnvironment.codexPath } : {}),
...(nativeEnvironment?.pluginDir && target === 'claude' ? { args: ['--plugin-dir', nativeEnvironment.pluginDir, ...adapters.claude.args] } : {}) };
function verifyLaunch() {
assertCurrent();
if (nativeEnvironment && require('./context-profile-native-executable').fingerprintExecutable(adapter.command).digest
@@ -14,11 +14,14 @@ function nativeFormat(header) {
|| header.subarray(0, 2).toString() === 'MZ';
}
function resolveExecutable(command) {
const LABELS = { codex: 'Codex', claude: 'Claude Code' };
function resolveExecutable(command, name = 'codex') {
if (!Object.hasOwn(LABELS, name)) throw new Error(`Unsupported native executable: ${name}`);
const candidate = path.isAbsolute(command) ? command : (process.env.PATH || '').split(path.delimiter)
.filter(directory => path.isAbsolute(directory)).map(directory => path.join(directory, process.platform === 'win32' ? 'codex.exe' : 'codex'))
.filter(directory => path.isAbsolute(directory)).map(directory => path.join(directory, process.platform === 'win32' ? `${name}.exe` : name))
.find(file => fs.existsSync(file));
if (!candidate) throw new Error('Native Codex executable was not found');
if (!candidate) throw new Error(`Native ${LABELS[name]} executable was not found`);
let executable = fs.realpathSync(candidate);
const before = io.inspect(executable);
if (!before.stat.isFile() || before.stat.nlink !== 1 || before.stat.size < 4 || before.stat.size > MAX_BYTES) {
@@ -33,7 +36,7 @@ function resolveExecutable(command) {
} finally { fs.closeSync(fd); }
if (!nativeFormat(header)) {
// Supported npm distribution: bind its platform binary, never only its JS shim.
if (path.basename(executable) !== 'codex.js') throw new Error('Native adapter requires a native Codex executable');
if (name !== 'codex' || path.basename(executable) !== 'codex.js') throw new Error(`Native adapter requires a native ${LABELS[name]} executable`);
const packageName = `@openai/codex-${process.platform}-${process.arch}`;
let manifest;
try { manifest = createRequire(executable).resolve(`${packageName}/package.json`); }
+187 -60
View File
@@ -14,15 +14,38 @@ const { digestObject, stableStringify, validateSchema } = require('./context-pro
const { discoverSync } = require('./context-profile-native-discovery');
const { fingerprintExecutable, resolveExecutable } = require('./context-profile-native-executable');
const VERSION = '0.154.0';
// 0.155.1: credential-free native-probe verified Lean, include, Full exclusion and resource relocation.
const SUPPORTED_VERSIONS = ['0.154.0', '0.155.1'];
const DIGEST = /^[a-f0-9]{64}$/;
const ID = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
const KEYS = new Set(['stateRoot', 'nativeRoot', 'expectedRevision', 'expectedCarrierDigest', 'codexPath']);
const CONTROLS = ['marketplace', 'project', 'home/.agents', 'home/.codex/config.toml',
'home/.codex/AGENTS.md', 'home/.codex/AGENTS.override.md', 'home/.codex/hooks.json',
'home/.codex/requirements.toml', 'home/.codex/plugins', 'home/.codex/skills'];
const KEYS = new Set(['stateRoot', 'nativeRoot', 'expectedRevision', 'expectedCarrierDigest', 'codexPath', 'claudePath']);
const PLUGIN = 'ecc-context-carrier';
// Claude loads the carrier per session with --plugin-dir; nothing is installed.
// Official marketplace auto-install would add plugins to the isolated home.
const CLAUDE_ISOLATION_ENV = Object.freeze({ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: '1' });
const PROVIDERS = Object.freeze({
codex: { label: 'Codex', command: 'codex', executableKey: 'codexPath',
// 0.155.1: credential-free native-probe verified Lean, include, Full exclusion and resource relocation.
requiredVersion: '0.154.0', versions: ['0.154.0', '0.155.1'], parseVersion: output => /^codex-cli (\S+)$/.exec(output)?.[1] ?? null,
admits(version) { return this.versions.includes(version); },
bootstrapPath: 'home/.codex/AGENTS.md',
controls: ['marketplace', 'project', 'home/.agents', 'home/.codex/config.toml',
'home/.codex/AGENTS.md', 'home/.codex/AGENTS.override.md', 'home/.codex/hooks.json',
'home/.codex/requirements.toml', 'home/.codex/plugins', 'home/.codex/skills'] },
// 2.1.292 and 2.1.293: credential-free probes verified Lean and Full session-only discovery and
// host projections. Later 2.1 patches are admitted; every check fails closed on output drift.
claude: { label: 'Claude', command: 'claude', executableKey: 'claudePath',
requiredVersion: '2.1.292', versions: ['>=2.1.292 <2.2.0'], parseVersion: output => /^(2\.1\.(0|[1-9]\d*)) \(Claude Code\)$/.exec(output)?.[1] ?? null,
admits(version) { return /^2\.1\.(0|[1-9]\d*)$/.test(version) && Number(version.split('.')[2]) >= 292; },
bootstrapPath: 'home/.claude/CLAUDE.md',
// Provider runtime state (.claude.json, projects, backups, plugin bookkeeping)
// is not skill discovery state; plugin registration is re-verified on prepare.
controls: ['plugin', 'project', 'home/.claude/CLAUDE.md', 'home/.claude/settings.json',
'home/.claude/settings.local.json', 'home/.claude/skills', 'home/.claude/agents', 'home/.claude/commands'] },
});
const providerFor = target => {
if (!Object.hasOwn(PROVIDERS, target)) throw new Error('Native preparation requires a configured, recovered Codex or Claude managed store');
return PROVIDERS[target];
};
const exists = file => Boolean(fs.lstatSync(file, { throwIfNoEntry: false }));
const equal = (a, b) => stableStringify(a) === stableStringify(b);
const inside = (a, b) => a === b || a.startsWith(`${b}${path.sep}`);
@@ -50,9 +73,10 @@ function inputs(options) {
if (!options || typeof options !== 'object' || Array.isArray(options)) throw new Error('Native profile options must be an object');
for (const key of Object.keys(options)) if (!KEYS.has(key)) throw new Error(`Unknown native profile option: ${key}`);
const { nativeRoot, stateRoot } = options;
const providerHomes = [path.join(os.homedir(), '.codex'), process.env.CODEX_HOME,
path.join(os.homedir(), '.claude'), process.env.CLAUDE_CONFIG_DIR].filter(Boolean);
if (typeof nativeRoot !== 'string' || !path.isAbsolute(nativeRoot) || path.resolve(nativeRoot) !== nativeRoot
|| nativeRoot === path.parse(nativeRoot).root || nativeRoot === os.homedir()
|| nativeRoot === path.join(os.homedir(), '.codex') || nativeRoot === process.env.CODEX_HOME) {
|| nativeRoot === path.parse(nativeRoot).root || nativeRoot === os.homedir() || providerHomes.includes(nativeRoot)) {
throw new Error('nativeRoot must be an explicit dedicated isolated root');
}
if (typeof stateRoot !== 'string' || !path.isAbsolute(stateRoot)) throw new Error('Managed stateRoot is required');
@@ -62,7 +86,7 @@ function inputs(options) {
const canonicalNative = exists(nativeRoot) ? fs.realpathSync(nativeRoot)
: path.join(fs.realpathSync(path.dirname(nativeRoot)), path.basename(nativeRoot));
const normalized = value => process.platform === 'win32' || process.platform === 'darwin' ? value.toLowerCase() : value;
const forbidden = [os.homedir(), path.join(os.homedir(), '.codex'), process.env.CODEX_HOME].filter(Boolean);
const forbidden = [os.homedir(), ...providerHomes];
if (forbidden.some(file => normalized(exists(file) ? fs.realpathSync(file) : file) === normalized(canonicalNative))) {
throw new Error('nativeRoot must be an explicit dedicated isolated root');
}
@@ -73,10 +97,14 @@ function inputs(options) {
throw new Error('Invalid native expected revision');
}
if (options.expectedCarrierDigest !== undefined && !DIGEST.test(options.expectedCarrierDigest)) throw new Error('Invalid native expected carrier digest');
if (options.codexPath !== undefined && (typeof options.codexPath !== 'string'
|| (options.codexPath !== 'codex' && !path.isAbsolute(options.codexPath)))) throw new Error('codexPath must be codex or an absolute executable path');
for (const { command, executableKey } of Object.values(PROVIDERS)) {
const value = options[executableKey];
if (value !== undefined && (typeof value !== 'string' || (value !== command && !path.isAbsolute(value)))) {
throw new Error(`${executableKey} must be ${command} or an absolute executable path`);
}
}
io.inspect(nativeRoot, true);
return { ...options, codexPath: options.codexPath || 'codex' };
return { ...options, codexPath: options.codexPath || 'codex', claudePath: options.claudePath || 'claude' };
}
function owner(options, create = false) {
@@ -96,9 +124,10 @@ function owner(options, create = false) {
function currentStore(options) {
const current = getStoreStatus({ stateRoot: options.stateRoot });
if (!current.configured || current.recoveryRequired || current.target !== 'codex') {
throw new Error('Native preparation requires a configured, recovered Codex managed store');
if (!current.configured || current.recoveryRequired) {
throw new Error('Native preparation requires a configured, recovered Codex or Claude managed store');
}
providerFor(current.target);
if (options.expectedCarrierDigest && current.carrierDigest !== options.expectedCarrierDigest) throw new Error('Managed carrier digest changed since preview');
return current;
}
@@ -123,8 +152,8 @@ function readState(options) {
return state;
}
function snapshot(root) {
return CONTROLS.map(relative => {
function snapshot(root, provider) {
return provider.controls.map(relative => {
const file = path.join(root, relative);
if (relative === 'home/.codex/config.toml') {
// Provider-owned runtime config: compare discovery-relevant state only
@@ -148,36 +177,48 @@ function snapshot(root) {
function loadReceipt(options, state, { allowRefresh = false } = {}) {
const root = generation(options, state.generationId);
const receipt = io.readJson(path.join(root, 'receipt.json'));
// Receipts written before Claude support carry no target and are Codex receipts.
const target = receipt.target ?? 'codex';
if (digestObject(receipt) !== state.generationReceiptDigest || receipt.schemaVersion !== 'ecc.native-context-receipt.v1'
|| receipt.generationId !== state.generationId || !SUPPORTED_VERSIONS.includes(receipt.providerVersion)
|| receipt.generationId !== state.generationId || !Object.hasOwn(PROVIDERS, target)
|| !PROVIDERS[target].admits(receipt.providerVersion)
|| receipt.bindingDigest !== digestObject({ nativeRoot: options.nativeRoot, stateRoot: options.stateRoot })) {
throw new Error('Native receipt integrity failed');
}
const provider = PROVIDERS[target];
const carrier = io.readJson(path.join(root, 'carrier.json'));
validateSchema(carrier, 'context-carrier.schema.json');
const { carrierDigest, ...body } = carrier;
if (carrierDigest !== receipt.carrierDigest || digestObject(body) !== carrierDigest) throw new Error('Native carrier digest integrity failed');
if (!equal(snapshot(root), receipt.controls)) throw new Error('Native discovery configuration or skill bytes changed');
if (!equal(snapshot(root, provider), receipt.controls)) throw new Error('Native discovery configuration or skill bytes changed');
if (!allowRefresh && (!receipt.executable || !equal(fingerprintExecutable(receipt.executable.path), receipt.executable))) {
throw new Error('Native Codex executable changed since preparation');
throw new Error(`Native ${provider.label} executable changed since preparation`);
}
if (receipt.bootstrap) {
if (receipt.bootstrap.stateRoot !== options.stateRoot || receipt.bootstrap.nativeRoot !== options.nativeRoot
|| receipt.bootstrap.carrierDigest !== receipt.carrierDigest) throw new Error('Interactive root binding integrity failed');
if (!allowRefresh) require('./context-profile-interactive').verifyBootstrap(receipt.bootstrap);
}
return { receipt, carrier, root };
return { receipt, carrier, root, target };
}
function providerPaths(target, root) {
if (target === 'claude') {
return { codexHome: null, claudeConfigDir: root && path.join(root, 'home/.claude'), pluginDir: root && path.join(root, 'plugin') };
}
return { codexHome: root && path.join(root, 'home/.codex') };
}
function response(options, state, current, pending = false, allowRefresh = false) {
const base = { schemaVersion: 'ecc.native-context-status.v1', nativeRoot: options.nativeRoot,
stateRoot: options.stateRoot, active: false, ready: false, revision: state?.revision || 0,
status: pending ? 'recovery-required' : 'unconfigured', target: 'codex',
providerVersion: VERSION, home: null, codexHome: null, carrierDigest: null, storeRevision: null,
status: pending ? 'recovery-required' : 'unconfigured', target: current.target,
providerVersion: providerFor(current.target).requiredVersion, home: null, ...providerPaths(current.target, null),
carrierDigest: null, storeRevision: null,
currentStoreRevision: current.revision, currentCarrierDigest: current.carrierDigest,
discovery: 'unobserved', currentSessionChanged: false, credentialsCopied: false };
if (!state) return base;
const { receipt, carrier, root } = loadReceipt(options, state, { allowRefresh });
const { receipt, carrier, root, target } = loadReceipt(options, state, { allowRefresh });
let bindingsMatch = true;
if (allowRefresh) {
try {
@@ -185,12 +226,14 @@ function response(options, state, current, pending = false, allowRefresh = false
if (receipt.bootstrap) require('./context-profile-interactive').verifyBootstrap(receipt.bootstrap);
} catch { bindingsMatch = false; }
}
const matches = state.storeRevision === current.revision && receipt.carrierDigest === current.carrierDigest;
const matches = state.storeRevision === current.revision && receipt.carrierDigest === current.carrierDigest
&& target === current.target;
return { ...base, status: pending ? 'recovery-required' : !bindingsMatch ? 'refresh-required' : matches ? 'ready' : 'stale',
ready: matches && bindingsMatch && !pending, providerVersion: receipt.providerVersion, bootstrap: receipt.bootstrap || null,
home: path.join(root, 'home'), codexHome: path.join(root, 'home/.codex'),
ready: matches && bindingsMatch && !pending, target, providerVersion: receipt.providerVersion, bootstrap: receipt.bootstrap || null,
home: path.join(root, 'home'), ...providerPaths(target, root),
carrierDigest: receipt.carrierDigest, storeRevision: state.storeRevision,
codexPath: receipt.executable.path, executable: receipt.executable.path, executableDigest: receipt.executable.digest,
[PROVIDERS[target].executableKey]: receipt.executable.path, executable: receipt.executable.path, executableDigest: receipt.executable.digest,
...(receipt.nativeObservation ? { nativeObservation: receipt.nativeObservation } : {}),
selectedIds: carrier.selectedIds, discovery: 'verified', evidenceScope: 'native-preparation-with-current-file-integrity',
activation: 'isolated-home-ready-for-new-session', modelInvocation: 'unobserved' };
}
@@ -208,33 +251,91 @@ function previewNativeProfile(input) {
exists(path.join(options.nativeRoot, 'pending.json')) || exists(path.join(options.nativeRoot, '.lock')), true)
: response(options, null, current);
if (options.expectedRevision !== undefined && options.expectedRevision !== before.revision) throw new Error('Native revision changed since preview');
const provider = providerFor(current.target);
return { ...before, status: 'proposed', ready: false, proposedCarrierDigest: current.carrierDigest,
proposedStoreRevision: current.revision, requiredProviderVersion: VERSION, supportedProviderVersions: [...SUPPORTED_VERSIONS] };
proposedStoreRevision: current.revision, requiredProviderVersion: provider.requiredVersion,
supportedProviderVersions: [...provider.versions] };
}
function environment(root) {
const env = { PATH: process.env.PATH, HOME: path.join(root, 'home'), CODEX_HOME: path.join(root, 'home/.codex'), LANG: 'C.UTF-8' };
function environment(root, target) {
const env = { PATH: process.env.PATH, HOME: path.join(root, 'home'), LANG: 'C.UTF-8',
...(target === 'claude' ? { CLAUDE_CONFIG_DIR: path.join(root, 'home/.claude'), ...CLAUDE_ISOLATION_ENV }
: { CODEX_HOME: path.join(root, 'home/.codex') }) };
if (process.platform === 'win32' && process.env.SystemRoot) env.SystemRoot = process.env.SystemRoot;
return env;
}
function command(options, root, args, dependencies) {
if (options.executableBinding && !equal(fingerprintExecutable(options.codexPath), options.executableBinding)) {
const { label } = providerFor(options.target);
if (options.executableBinding && !equal(fingerprintExecutable(options.executablePath), options.executableBinding)) {
throw new Error('Native executable changed before provider call');
}
const result = (dependencies.execute || spawnSync)(options.codexPath, args, {
cwd: path.join(root, 'project'), env: environment(root), encoding: 'utf8', shell: false,
const result = (dependencies.execute || spawnSync)(options.executablePath, args, {
cwd: path.join(root, 'project'), env: environment(root, options.target), encoding: 'utf8', shell: false,
timeout: 30000, killSignal: 'SIGKILL', maxBuffer: 2 * 1024 * 1024 });
if (result.error || result.status !== 0) throw new Error('Native Codex command failed; isolated attempt retained for recovery');
if (typeof result.stdout !== 'string' || Buffer.byteLength(result.stdout) > 2 * 1024 * 1024) throw new Error('Native Codex command output exceeded its bound');
if (result.error || result.status !== 0) throw new Error(`Native ${label} command failed; isolated attempt retained for recovery`);
if (typeof result.stdout !== 'string' || Buffer.byteLength(result.stdout) > 2 * 1024 * 1024) throw new Error(`Native ${label} command output exceeded its bound`);
return result.stdout.trim();
}
function verifyFiles(directory, carrier) {
const observed = io.inventory(directory).files.sort((a, b) => a.path.localeCompare(b.path));
const expected = carrier.files.map(file => ({ path: file.destinationPath, bytes: file.bytes, digest: file.digest }))
.sort((a, b) => a.path.localeCompare(b.path));
if (!equal(observed, expected)) throw new Error('Native installed file set or digest mismatch');
}
function parseJson(text, message) {
try { return JSON.parse(text); } catch { throw new Error(message); }
}
function componentLine(lines, label) {
const match = lines.map(line => line.trim()).map(line => new RegExp(`^${label} \\((\\d+)\\)(?:\\s+(.*))?$`).exec(line)).find(Boolean);
if (!match) throw new Error('Native Claude component discovery mismatch');
return { count: Number(match[1]), rest: match[2] || '' };
}
// `claude plugin details` has no JSON form; the pinned version fixes this layout.
function parsePluginDetails(text) {
const lines = text.split(/\r?\n/);
const skills = componentLine(lines, 'Skills');
const names = skills.count ? skills.rest.split(',').map(name => name.trim()) : [];
const counts = ['Agents', 'Hooks', 'MCP servers', 'LSP servers'].map(label => componentLine(lines, label).count);
const projection = lines.map(line => /^\s*Always-on:\s+~?(\d{1,3}(?:,\d{3})*|\d+)(k?) tok\b/.exec(line)).find(Boolean);
if (!projection) throw new Error('Native Claude token projection is unparseable');
const tokens = Number(projection[1].replace(/,/g, '')) * (projection[2] ? 1000 : 1);
return { skills: names.length === skills.count ? names.sort() : null, extraComponents: counts.some(count => count !== 0), tokens };
}
function verifyClaude(options, root, carrier, dependencies) {
if (PROVIDERS.claude.parseVersion(command(options, root, ['--version'], dependencies)) !== options.providerVersion) {
throw new Error('Native Claude version changed since verification');
}
const pluginDir = path.join(root, 'plugin');
const report = parseJson(command(options, root, ['plugin', 'validate', '--json', pluginDir], dependencies), 'Native Claude plugin validation failed');
if (report?.success !== true || !Array.isArray(report.manifest?.errors) || report.manifest.errors.length
|| !Array.isArray(report.contents) || !report.contents.every(item => Array.isArray(item?.errors) && item.errors.length === 0)) {
throw new Error('Native Claude plugin validation failed');
}
const listed = parseJson(command(options, root, ['--plugin-dir', pluginDir, 'plugin', 'list', '--json'], dependencies),
'Native Claude plugin discovery mismatch');
if (!Array.isArray(listed) || listed.length !== 1 || listed[0]?.id !== `${PLUGIN}@inline` || listed[0].scope !== 'session'
|| listed[0].enabled !== true || listed[0].installPath !== pluginDir) throw new Error('Native Claude plugin discovery mismatch');
const details = parsePluginDetails(command(options, root, ['--plugin-dir', pluginDir, 'plugin', 'details', PLUGIN], dependencies));
if (details.extraComponents || !equal(details.skills, carrier.entries.map(entry => entry.name).sort())) {
throw new Error('Native Claude component discovery mismatch');
}
verifyFiles(pluginDir, carrier);
return { surface: 'plugin-always-on-listing', method: `claude-plugin-details@${options.providerVersion}`,
tokens: details.tokens, approximate: true };
}
function verifyNative(options, root, carrier, dependencies) {
if (options.target === 'claude') return verifyClaude(options, root, carrier, dependencies);
if (command(options, root, ['--version'], dependencies) !== `codex-cli ${options.providerVersion}`) throw new Error('Native Codex version changed since verification');
const env = environment(root); const marketplaceName = `ecc-context-${carrier.carrierDigest.slice(0, 16)}`;
const env = environment(root, 'codex'); const marketplaceName = `ecc-context-${carrier.carrierDigest.slice(0, 16)}`;
const cache = path.join(env.CODEX_HOME, 'plugins/cache', marketplaceName, 'ecc-context-carrier/local');
const result = (dependencies.discover || discoverSync)(options.codexPath, { cwd: path.join(root, 'project'), env });
const result = (dependencies.discover || discoverSync)(options.executablePath, { cwd: path.join(root, 'project'), env });
if (!result || !Array.isArray(result.data) || result.data.length !== 1 || !equal(result.data[0].errors, [])
|| result.data[0].cwd !== path.join(root, 'project')
|| !Array.isArray(result.data[0].skills)) throw new Error('Native skill discovery shape, project binding or parser errors');
@@ -249,10 +350,8 @@ function verifyNative(options, root, carrier, dependencies) {
const name = skill.name.slice('ecc-context-carrier:'.length);
if (!skill.enabled || skill.path !== path.join(cache, 'skills', name, 'SKILL.md')) throw new Error('Native skill discovery enabled state or path mismatch');
}
const observed = io.inventory(cache).files.sort((a, b) => a.path.localeCompare(b.path));
const expected = carrier.files.map(file => ({ path: file.destinationPath, bytes: file.bytes, digest: file.digest }))
.sort((a, b) => a.path.localeCompare(b.path));
if (!equal(observed, expected)) throw new Error('Native installed file set or digest mismatch');
verifyFiles(cache, carrier);
return null;
}
function checkpoint(dependencies, point) { if (dependencies.onCheckpoint) dependencies.onCheckpoint(point); }
@@ -296,19 +395,40 @@ function publish(options, before, current, generationId, receipt, dependencies)
return response(options, state, current);
}
function register(options, root, carrier, current, dependencies) {
for (const relative of ['home', 'home/.codex', 'project', 'marketplace', 'marketplace/.agents', 'marketplace/.agents/plugins', 'marketplace/carrier']) {
io.mkdir(path.join(root, relative));
}
const version = command(options, root, ['--version'], dependencies);
const providerVersion = SUPPORTED_VERSIONS.find(value => version === `codex-cli ${value}`);
if (!providerVersion) throw new Error(`Native Codex version must be exactly ${SUPPORTED_VERSIONS.join(' or ')}`);
function copyCarrier(root, destination, carrier, current) {
for (const file of carrier.files) {
const relative = `marketplace/carrier/${file.destinationPath}`;
const relative = `${destination}/${file.destinationPath}`;
const bytes = io.read(path.join(current.generationRoot, file.destinationPath));
if (io.hash(bytes) !== file.digest || bytes.length !== file.bytes) throw new Error('Managed carrier source digest changed');
io.ensureParents(root, relative); io.writeExclusive(path.join(root, relative), bytes);
}
}
function providerVersionFor(options, root, dependencies) {
const provider = providerFor(options.target);
const version = command(options, root, ['--version'], dependencies);
const providerVersion = provider.parseVersion(version);
if (!providerVersion || !provider.admits(providerVersion)) {
throw new Error(`Native ${provider.label} version must be ${provider.versions.join(' or ')}`);
}
return providerVersion;
}
function registerClaude(options, root, carrier, current, dependencies) {
for (const relative of ['home', 'home/.claude', 'project', 'plugin']) io.mkdir(path.join(root, relative));
const providerVersion = providerVersionFor(options, root, dependencies);
copyCarrier(root, 'plugin', carrier, current);
checkpoint(dependencies, 'registered');
return { providerVersion, nativeObservation: verifyNative({ ...options, providerVersion }, root, carrier, dependencies) };
}
function register(options, root, carrier, current, dependencies) {
if (options.target === 'claude') return registerClaude(options, root, carrier, current, dependencies);
for (const relative of ['home', 'home/.codex', 'project', 'marketplace', 'marketplace/.agents', 'marketplace/.agents/plugins', 'marketplace/carrier']) {
io.mkdir(path.join(root, relative));
}
const providerVersion = providerVersionFor(options, root, dependencies);
copyCarrier(root, 'marketplace/carrier', carrier, current);
const name = `ecc-context-${carrier.carrierDigest.slice(0, 16)}`;
io.writeExclusive(path.join(root, 'marketplace/.agents/plugins/marketplace.json'), io.jsonBytes({ name,
plugins: [{ name: 'ecc-context-carrier', source: { source: 'local', path: './carrier' },
@@ -317,22 +437,23 @@ function register(options, root, carrier, current, dependencies) {
command(options, root, ['plugin', 'add', `ecc-context-carrier@${name}`, '--json'], dependencies);
checkpoint(dependencies, 'registered');
verifyNative({ ...options, providerVersion }, root, carrier, dependencies);
return providerVersion;
return { providerVersion, nativeObservation: null };
}
function prepareNativeProfile(input, dependencies = {}) {
let options = inputs(input); const current = currentStore(options);
previewNativeProfile(options);
const executable = resolveExecutable(options.codexPath);
const provider = providerFor(current.target);
const executable = resolveExecutable(options[provider.executableKey], provider.command);
owner(options, true);
options = { ...options, codexPath: executable.path, executableBinding: executable };
options = { ...options, target: current.target, executablePath: executable.path, executableBinding: executable };
return locked(options, false, () => {
if (exists(path.join(options.nativeRoot, 'pending.json'))) throw new Error('Native attempt requires recovery');
const before = readState(options);
if (options.expectedRevision !== undefined && options.expectedRevision !== (before?.revision || 0)) throw new Error('Native revision changed since preview');
const previous = before ? loadReceipt(options, before, { allowRefresh: true }) : null;
const bootstrap = require('./context-profile-interactive').bootstrapFor(options, current);
if (before && before.storeRevision === current.revision) {
if (before && before.storeRevision === current.revision && previous.target === current.target) {
if (previous.receipt.carrierDigest === current.carrierDigest && equal(previous.receipt.executable, executable) && equal(previous.receipt.bootstrap, bootstrap.binding)) {
verifyNative({ ...options, providerVersion: previous.receipt.providerVersion }, previous.root, previous.carrier, dependencies);
recheckStore(options, current);
@@ -350,11 +471,13 @@ function prepareNativeProfile(input, dependencies = {}) {
const { carrierDigest, ...body } = carrier;
if (carrierDigest !== current.carrierDigest || digestObject(body) !== carrierDigest) throw new Error('Managed carrier descriptor changed before native registration');
io.writeExclusive(path.join(root, 'carrier.json'), io.jsonBytes(carrier));
const providerVersion = register(options, root, carrier, current, dependencies);
io.writeExclusive(path.join(root, 'home/.codex/AGENTS.md'), bootstrap.bytes);
const { providerVersion, nativeObservation } = register(options, root, carrier, current, dependencies);
io.writeExclusive(path.join(root, provider.bootstrapPath), bootstrap.bytes);
const receipt = { schemaVersion: 'ecc.native-context-receipt.v1', generationId,
bindingDigest: digestObject({ nativeRoot: options.nativeRoot, stateRoot: options.stateRoot }),
carrierDigest: carrier.carrierDigest, providerVersion, executable, bootstrap: bootstrap.binding, controls: snapshot(root) };
...(current.target === 'codex' ? {} : { target: current.target }),
carrierDigest: carrier.carrierDigest, providerVersion, executable, bootstrap: bootstrap.binding,
...(nativeObservation ? { nativeObservation } : {}), controls: snapshot(root, provider) };
io.writeExclusive(path.join(root, 'receipt.json'), io.jsonBytes(receipt));
checkpoint(dependencies, 'verified');
return publish(options, before, current, generationId, receipt, dependencies);
@@ -373,8 +496,11 @@ function rollbackNativeProfile(input, dependencies = {}) {
const receipt = io.readJson(path.join(root, 'receipt.json'));
const previous = loadReceipt(options, { generationId: before.previousGenerationId,
generationReceiptDigest: before.previousGenerationReceiptDigest });
if (receipt.carrierDigest !== current.carrierDigest) throw new Error('Rollback the managed store to the previous native carrier first');
verifyNative({ ...options, providerVersion: receipt.providerVersion, codexPath: receipt.executable.path, executableBinding: receipt.executable }, root, previous.carrier, dependencies);
if (receipt.carrierDigest !== current.carrierDigest || previous.target !== current.target) {
throw new Error('Rollback the managed store to the previous native carrier first');
}
verifyNative({ ...options, target: previous.target, providerVersion: receipt.providerVersion,
executablePath: receipt.executable.path, executableBinding: receipt.executable }, root, previous.carrier, dependencies);
io.atomicJson(path.join(options.nativeRoot, 'pending.json'), { schemaVersion: 'ecc.native-context-pending.v1',
before, generationId: before.previousGenerationId, carrierDigest: current.carrierDigest, storeRevision: current.revision });
return publish(options, before, current, before.previousGenerationId, receipt, dependencies);
@@ -400,4 +526,5 @@ function recoverNativeProfile(input) {
});
}
module.exports = { getNativeProfileStatus, prepareNativeProfile, previewNativeProfile, recoverNativeProfile, rollbackNativeProfile };
module.exports = { CLAUDE_ISOLATION_ENV, getNativeProfileStatus, prepareNativeProfile, previewNativeProfile,
recoverNativeProfile, rollbackNativeProfile };
+4 -3
View File
@@ -30,7 +30,7 @@ Experimental managed profiles and bounded task context:
ecc profile resolve [lean|full] --task-input task.json|- [--load] [--previous receipt.json] [--json]
ecc profile run --task-input task.json|- [--state-root <directory>] [--target codex|claude] [--dry-run] [--json]
ecc profile set lean|full --state-root <dedicated-directory> [--selection auto|manual|suggest]
[--target codex] [--include skill:<id>] [--exclude skill:<id>] [--dry-run] [--json]
[--target codex|claude] [--include skill:<id>] [--exclude skill:<id>] [--dry-run] [--json]
ecc profile status --state-root <directory> [--json]
ecc profile mode auto|manual|suggest --state-root <directory> [--dry-run] [--json]
ecc profile rollback --state-root <directory> [--expected-revision N] [--json]
@@ -42,11 +42,12 @@ Experimental managed profiles and bounded task context:
ecc profile native-recover --state-root <directory> --native-root <directory> [--json]
Task input may be one bounded UTF-8 JSON object on stdin with --task-input - (65536 bytes maximum).
Start requires prepare-native, launches the pinned native TUI with inherited stdio and provider permissions,
and reads a receipt-bound isolated AGENTS bootstrap. Authenticate separately in the isolated home; credentials are never copied.
and reads a receipt-bound isolated bootstrap (Codex AGENTS.md, Claude CLAUDE.md). Claude loads the carrier with --plugin-dir;
nothing is installed. Authenticate separately in the isolated home; credentials are never copied.
Set stages owned generations; provider discovery is verified separately.
Resolve returns context only with --load; suggest and --dry-run never return skill bodies.
Use resolve --state-root <directory> to honor the saved base, mode and exclusions.
Run with --native-root to use a verified isolated Codex generation. Existing sessions are unchanged.
Run with --native-root to use a verified isolated Codex or Claude generation. Existing sessions are unchanged.
`;
}