This branch snapshots the in-progress desktop workspace so GitHub-hosted Windows and Linux runners can package the same source tree that already passes local verification on macOS. Constraint: Local Apple Silicon Docker/Wine packaging crashes before Windows artifacts are produced Rejected: Publish from the incomplete base repo state | remote typecheck failed because current source additions were missing Confidence: medium Scope-risk: broad Directive: Treat this branch as a build snapshot only; do not merge it without reviewing the unrelated workspace changes it contains Tested: local typecheck, lint, workspace typecheck, unit tests, integration tests, e2e smoke, macOS signed release upload Not-tested: Native Windows/Linux artifacts before this CI run
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
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: <pkg>/dist/server/ -> <pkg>/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<string, string>, key: string): boolean {
|
|
const raw = env[key];
|
|
return typeof raw === "string" && raw.trim().length > 0;
|
|
}
|
|
|
|
export function resolveCodexBillingType(env: Record<string, string>): "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<string | null> {
|
|
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`,
|
|
);
|
|
}
|
|
}
|
|
}
|