import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import type { AdapterExecutionContext, ManagedSkillRef } from "@agentcompany/adapter-utils"; import { createManagedSkillSymlinks, } from "@agentcompany/adapter-utils/server-utils"; const __moduleDir = path.dirname(fileURLToPath(import.meta.url)); export const SKILLS_CANDIDATES = [ path.resolve(__moduleDir, "../../skills"), // published: /dist/server/ -> /skills/ path.resolve(__moduleDir, "../../../../../skills"), // dev: src/server/ -> repo root/skills/ ]; export const CODEX_ROLLOUT_NOISE_RE = /^\d{4}-\d{2}-\d{2}T[^\s]+\s+ERROR\s+codex_core::rollout::list:\s+state db missing rollout path for thread\s+[a-z0-9-]+$/i; export function stripCodexRolloutNoise(text: string): string { const parts = text.split(/\r?\n/); const kept: string[] = []; for (const part of parts) { const trimmed = part.trim(); if (!trimmed) { kept.push(part); continue; } if (CODEX_ROLLOUT_NOISE_RE.test(trimmed)) continue; kept.push(part); } return kept.join("\n"); } export function hasNonEmptyEnvValue(env: Record, key: string): boolean { const raw = env[key]; return typeof raw === "string" && raw.trim().length > 0; } export function resolveCodexBillingType(env: Record): "api" | "subscription" { // Codex uses API-key auth when OPENAI_API_KEY is present; otherwise rely on local login/session auth. return hasNonEmptyEnvValue(env, "OPENAI_API_KEY") ? "api" : "subscription"; } export function codexHomeDir(): string { const fromEnv = process.env.CODEX_HOME; if (typeof fromEnv === "string" && fromEnv.trim().length > 0) return fromEnv.trim(); return path.join(os.homedir(), ".codex"); } export async function resolveAgentCompanySkillsDir(): Promise { for (const candidate of SKILLS_CANDIDATES) { const isDir = await fs.stat(candidate).then((s) => s.isDirectory()).catch(() => false); if (isDir) return candidate; } return null; } export async function ensureCodexSkillsInjected( managedSkills: ManagedSkillRef[], onLog: AdapterExecutionContext["onLog"], ) { const skillsHome = path.join(codexHomeDir(), "skills"); // Use managed skills when available; fall back to static repo scan if (managedSkills.length > 0) { await createManagedSkillSymlinks(managedSkills, skillsHome, onLog); return; } const skillsDir = await resolveAgentCompanySkillsDir(); if (!skillsDir) return; await fs.mkdir(skillsHome, { recursive: true }); const entries = await fs.readdir(skillsDir, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const source = path.join(skillsDir, entry.name); const target = path.join(skillsHome, entry.name); const existing = await fs.lstat(target).catch(() => null); if (existing) continue; try { await fs.symlink(source, target); await onLog( "stderr", `[agent-company] Injected Codex skill "${entry.name}" into ${skillsHome}\n`, ); } catch (err) { await onLog( "stderr", `[agent-company] Failed to inject Codex skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`, ); } } }