feat(#2088): migrate Codex onto the Embeddable Orchestration System (ADR-1239)
Drive Codex install/uninstall through the descriptor-driven Host-Integration Interface (declarative embedding adapter → engine surface dispatch) and fold every positive `runtime === 'codex'` / `isCodex` projection into descriptor-driven `runtime.hostBehaviors`. Install/uninstall output stays byte-parity-gated (tests/fixtures/golden-install-parity/codex.json); no other runtime changes. Three Context7-verified upgrades, each with a test on the user-reachable surface: - Skill root → canonical $HOME/.agents/skills via a skills-kind `home` override, with pre-move migration cleanup (stale ~/.codex/skills/gsd-* removed on install and uninstall; user content preserved). Fixes getGlobalSkillsBase, writeManifest, and the skill-manifest inventory to honor the override so --skills-root / sync-skills / the manifest report the real location. - Six new hooks.json lifecycle events (PreToolUse, PermissionRequest, PreCompact, PostCompact, SubagentStop, UserPromptSubmit) shared by install + uninstall; extendedHookEvents reconciled [] -> the schema-valid wired subset. - Explicit `[agents] max_depth = 1` in the managed config.toml block, pinning the negotiated dispatch.maxDepth:1 axis. validateCodexConfigSchema now permits a known-scalar-only bare `[agents]` AgentsToml table (still rejects [[agents]] and unknown-key break-forms, #2760); mergeCodexConfig preserves the user's own AgentsToml scalars (max_threads etc.) instead of dropping them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,10 @@ const {
|
||||
installRuntimeArtifacts,
|
||||
} = require('../gsd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
const {
|
||||
resolveRuntimeArtifactLayout,
|
||||
} = require('../gsd-core/bin/lib/runtime-artifact-layout.cjs');
|
||||
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
|
||||
const {
|
||||
@@ -58,16 +62,55 @@ const FLAT = [
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Codex resolves its skills-kind destination via os.homedir() + a 'skills'-kind
|
||||
* 'home: ".agents"' layout override (ADR-1239 EoS upgrade 3, #2088), NOT
|
||||
* tmpDir/skills. Sandbox HOME/USERPROFILE to tmpDir for the duration of the
|
||||
* synchronous callback so codex's resolved skills dir becomes
|
||||
* tmpDir/.agents/skills instead of the developer's real ~/.agents/skills.
|
||||
*/
|
||||
function withSandboxedHome(tmpDir, fn) {
|
||||
const savedHome = process.env.HOME;
|
||||
const savedUserProfile = process.env.USERPROFILE;
|
||||
process.env.HOME = tmpDir;
|
||||
process.env.USERPROFILE = tmpDir;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
if (savedHome === undefined) delete process.env.HOME;
|
||||
else process.env.HOME = savedHome;
|
||||
if (savedUserProfile === undefined) delete process.env.USERPROFILE;
|
||||
else process.env.USERPROFILE = savedUserProfile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fresh temp dir, run installRuntimeArtifacts into it, and return
|
||||
* the tmpDir path. Caller must cleanup in finally.
|
||||
* the tmpDir path. Caller must cleanup in finally. HOME is sandboxed to
|
||||
* tmpDir for the install call so codex never writes to the real ~/.agents/skills.
|
||||
*/
|
||||
function runInstall(runtime, scope, resolved) {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-nest-test-${runtime}-`));
|
||||
installRuntimeArtifacts(runtime, tmpDir, scope, resolved);
|
||||
withSandboxedHome(tmpDir, () => installRuntimeArtifacts(runtime, tmpDir, scope, resolved));
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the skills-kind destination directory for a runtime, honoring the
|
||||
* skills-kind 'home' override (codex only — resolves under tmpDir/.agents
|
||||
* once HOME is sandboxed). All other runtimes have no 'home' override, so
|
||||
* this falls back to tmpDir/<destSubpath>, matching the static skillsSub
|
||||
* tables above.
|
||||
*/
|
||||
function resolveSkillsDir(runtime, tmpDir, scope) {
|
||||
return withSandboxedHome(tmpDir, () => {
|
||||
const layout = resolveRuntimeArtifactLayout(runtime, tmpDir, scope);
|
||||
const skillsKind = layout.kinds.find((k) => k.kind === 'skills');
|
||||
assert.ok(skillsKind, `${runtime} must have skills kind`);
|
||||
return path.join(skillsKind.home || tmpDir, skillsKind.destSubpath);
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve the full profile once (shared by all installs)
|
||||
const MANIFEST = loadSkillsManifest(COMMANDS_GSD);
|
||||
const RESOLVED_FULL = resolveProfile({ modes: ['full'], manifest: MANIFEST });
|
||||
@@ -267,7 +310,7 @@ describe('claude: total top-level gsd- entries >= 60 (flat layout, #924)', () =>
|
||||
// FLAT runtimes: concrete skills stay top-level, no nesting
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const { runtime, scope, skillsSub } of FLAT) {
|
||||
for (const { runtime, scope } of FLAT) {
|
||||
describe(`${runtime} (flat layout)`, () => {
|
||||
let tmpDir;
|
||||
|
||||
@@ -282,7 +325,7 @@ for (const { runtime, scope, skillsSub } of FLAT) {
|
||||
});
|
||||
|
||||
test(`${runtime}: stays flat — concrete skills remain top-level, no nesting`, () => {
|
||||
const skillsDir = path.join(tmpDir, skillsSub);
|
||||
const skillsDir = resolveSkillsDir(runtime, tmpDir, scope);
|
||||
assert.ok(fs.existsSync(skillsDir), `skillsDir must exist: ${skillsDir}`);
|
||||
|
||||
const topLevel = fs.readdirSync(skillsDir);
|
||||
|
||||
Reference in New Issue
Block a user