Deliver the Codex asset-producer subagent reliably (#161)

Codex reads custom subagents from .codex/agents/*.toml, a directory
separate from where it reads skills (.agents/skills). Skill installers
(notably `npx skills add`, see vercel-labs/skills#1290) only carry the
skills/ subtree, so the asset-producer agent was never delivered.

- build: bundle the codex .toml inside the skill dir for the variants
  Codex loads as a skill (agents, codex), so it travels with the skill.
- cli: skills install/update now write .codex/agents/ for Codex-likely
  projects (a .agents target or a global ~/.codex); update heals a
  missing sidecar. Non-Codex projects are untouched.
- context.mjs: on boot under a Codex install, emit a self-healing
  CODEX_AGENT_MISSING directive pointing at the bundled copy when the
  project's .codex/agents/ definition is absent. Self-resolves on copy.

CLI 2.2.0 -> 2.3.0 (published). Skill stays 3.5.0 (unpublished); the
note is folded into the existing 3.5.0 changelog entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-05-28 15:07:40 -07:00
co-authored by Claude Opus 4.8
parent e58a4c571f
commit 7253b3870a
19 changed files with 1056 additions and 28 deletions
+64 -2
View File
@@ -218,9 +218,56 @@ async function computeUpdateDirective(now = Date.now()) {
}
}
// ─── Codex subagent self-heal ───────────────────────────────────────────────
// Codex reads custom subagents from .codex/agents/*.toml, a sibling of the
// skill dir that skill installers (notably `npx skills add`) don't carry. The
// build bundles the .toml inside the Codex skill variants, so when this module
// runs under a Codex install and the project is missing the subagent, point the
// agent at the bundled copy. Self-resolves the moment the file is copied.
const CODEX_AGENT_FILE = 'impeccable_asset_producer.toml';
/**
* Returns a one-time directive when (a) the running harness is Codex -- detected
* purely from this module's install path, since each harness loads its own skill
* copy -- and (b) the bundled subagent .toml exists but the project's
* .codex/agents/ copy does not. Returns null in every other case. Best-effort:
* any error yields null and never disrupts context output.
*/
export function computeCodexAgentDirective({ modulePath, fileExists = fs.existsSync } = {}) {
try {
if (!modulePath) return null;
const here = path.dirname(modulePath);
const skillRoot = path.resolve(here, '..');
const providerDir = path.basename(path.resolve(here, '..', '..', '..'));
const projectRoot = path.resolve(here, '..', '..', '..', '..');
// Only the variants Codex loads as a skill carry the bundled subagent.
if (providerDir !== '.agents' && providerDir !== '.codex') return null;
const bundledToml = path.join(skillRoot, 'agents', CODEX_AGENT_FILE);
const installedToml = path.join(projectRoot, '.codex', 'agents', CODEX_AGENT_FILE);
if (!fileExists(bundledToml)) return null; // older build without the bundled copy
if (fileExists(installedToml)) return null; // already installed -- nothing to do
const destDir = path.join(projectRoot, '.codex', 'agents');
return (
`CODEX_AGENT_MISSING: The Impeccable asset-producer subagent is not installed for Codex. ` +
`Codex reads custom subagents from .codex/agents/*.toml, but ${installedToml} is missing. ` +
`To enable it, copy the bundled definition once: ` +
`\`mkdir -p "${destDir}" && cp "${bundledToml}" "${destDir}/"\`. ` +
`This is optional and only affects the asset-production subagent; continue the current task either ` +
`way, and do not raise this again this session.`
);
} catch {
return null;
}
}
async function cli() {
const ctx = loadContext(process.cwd());
const updateDirective = await computeUpdateDirective();
const codexDirective = computeCodexAgentDirective({ modulePath: fileURLToPath(import.meta.url) });
if (!ctx.hasProduct) {
// Direct stdout message instead of relying on empty output as a signal
@@ -231,6 +278,7 @@ async function cli() {
'instructions to write PRODUCT.md before resuming.',
];
if (updateDirective) parts.push(updateDirective);
if (codexDirective) parts.push(codexDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
process.exit(0);
}
@@ -244,10 +292,24 @@ async function cli() {
: `NEXT STEP: You MUST now read the matching register reference (\`reference/brand.md\` or \`reference/product.md\`) before producing any design output. Pick based on PRODUCT.md above.`;
parts.push(next);
if (updateDirective) parts.push(updateDirective);
if (codexDirective) parts.push(codexDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
}
const _running = process.argv[1];
if (_running?.endsWith('context.mjs') || _running?.endsWith('context.mjs/')) {
// Run cli() only when this module is the entry point. Compare realpaths
// rather than endsWith(): a loose suffix match also fires for unrelated
// scripts like `load-context.mjs`, and realpath tolerates symlinked
// invocation (the test harness symlinks the skill dir).
function invokedAsScript() {
const arg = process.argv[1];
if (!arg) return false;
try {
return fs.realpathSync(arg) === fs.realpathSync(fileURLToPath(import.meta.url));
} catch {
return false;
}
}
if (invokedAsScript()) {
cli();
}