fix: resolve code queue runner skills path
Resolve Code Queue runner skills through approved source fallback while preserving hostPath contract diagnostics.
This commit is contained in:
@@ -9,7 +9,7 @@ import { extractRecord, extractString, terminalStatus, textInput, withCodeAgentG
|
||||
import { classifyRunnerError, runnerErrorClassificationJson } from "../runner-error-classifier";
|
||||
|
||||
export interface CodexPortContext {
|
||||
config: Pick<RuntimeConfig, "approvalPolicy" | "codexHome" | "defaultWorkdir" | "sandbox" | "sourceCodexConfig" | "turnNoActivityTimeoutMs">;
|
||||
config: Pick<RuntimeConfig, "approvalPolicy" | "codexHome" | "defaultWorkdir" | "resolvedRunnerSkillsPath" | "sandbox" | "sourceCodexConfig" | "turnNoActivityTimeoutMs">;
|
||||
activeRuns: Map<string, ActiveRun>;
|
||||
appendOutput: (task: QueueTask, channel: "system" | "assistant" | "reasoning" | "command" | "diff" | "tool" | "error", text: string, method?: string, itemId?: string, append?: boolean) => unknown;
|
||||
addEvent: (task: QueueTask, event: CodexEventSummary) => void;
|
||||
@@ -97,6 +97,7 @@ function codexAppServerEnv(task: QueueTask): NodeJS.ProcessEnv {
|
||||
...process.env,
|
||||
CODEX_HOME: ctx().config.codexHome,
|
||||
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "unidesk_code_queue",
|
||||
UNIDESK_SKILLS_PATH: ctx().config.resolvedRunnerSkillsPath(),
|
||||
};
|
||||
if (!shouldRunCodexDirect()) return withCodeAgentGitConfigEnv(env);
|
||||
for (const key of codexProxyEnvKeys) delete env[key];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { codeAgentGitConfigEntries, deepseekChatModel, extractRecord, minimaxM27
|
||||
import { classifyRunnerError, runnerErrorClassificationJson } from "../runner-error-classifier";
|
||||
|
||||
export interface OpenCodePortContext {
|
||||
config: Pick<RuntimeConfig, "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultWorkdir" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "turnNoActivityTimeoutMs">;
|
||||
config: Pick<RuntimeConfig, "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultWorkdir" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "resolvedRunnerSkillsPath" | "turnNoActivityTimeoutMs">;
|
||||
activeRuns: Map<string, ActiveRun>;
|
||||
addEvent: (task: QueueTask, event: CodexEventSummary) => void;
|
||||
appendOutput: (task: QueueTask, channel: "system" | "assistant" | "reasoning" | "command" | "diff" | "tool" | "error", text: string, method?: string, itemId?: string, append?: boolean) => unknown;
|
||||
@@ -140,6 +140,7 @@ function openCodeEnv(task: QueueTask): NodeJS.ProcessEnv {
|
||||
MINIMAX_API_BASE: ctx().config.minimaxApiBase,
|
||||
MINIMAX_MODEL: ctx().config.minimaxModel,
|
||||
OPENCODE_CONFIG_CONTENT: openCodeConfigContent(),
|
||||
UNIDESK_SKILLS_PATH: ctx().config.resolvedRunnerSkillsPath(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -402,6 +402,7 @@ function readConfig(): RuntimeConfig {
|
||||
executionProviderIds,
|
||||
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "DEEPSEEK_API_KEY", "DEEPSEEK_API_BASE", "DEEPSEEK_MODEL", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL", "GH_TOKEN", "GITHUB_TOKEN", "GH_HOST", "GITHUB_API_URL", "GH_REPO"]),
|
||||
skillsPath: envString("UNIDESK_SKILLS_PATH", "/root/.agents/skills"),
|
||||
resolvedRunnerSkillsPath,
|
||||
codexHome: envString("CODE_QUEUE_CODEX_HOME", "/var/lib/unidesk/code-queue/codex-home"),
|
||||
opencodeXdgDir: envString("CODE_QUEUE_OPENCODE_XDG_DIR", resolve(dataDir, "opencode-xdg")),
|
||||
sourceCodexConfig: envString("CODE_QUEUE_SOURCE_CODEX_CONFIG", "/root/.codex/config.toml"),
|
||||
@@ -2464,14 +2465,22 @@ function runProbe(command: string, args: string[], timeout = 3_000): { ok: boole
|
||||
return { ok: result.status === 0, output };
|
||||
}
|
||||
|
||||
function currentSkillAvailability() {
|
||||
return collectSkillAvailability({ target: config.skillsPath });
|
||||
}
|
||||
|
||||
function collectSkillsStatus(): JsonValue {
|
||||
return skillAvailabilityJson(collectSkillAvailability({ target: config.skillsPath }));
|
||||
return skillAvailabilityJson(currentSkillAvailability());
|
||||
}
|
||||
|
||||
function collectSkillsSyncPreflight(): JsonValue {
|
||||
return skillSyncPreflightJson(collectSkillSyncPreflight({ target: config.skillsPath }));
|
||||
}
|
||||
|
||||
function resolvedRunnerSkillsPath(): string {
|
||||
return currentSkillAvailability().resolution.runnerEnvValue;
|
||||
}
|
||||
|
||||
function collectDevReady(): JsonValue {
|
||||
const now = Date.now();
|
||||
if (devReadyCache !== null && now - devReadyCache.checkedAtMs < 30_000) return devReadyCache.value;
|
||||
@@ -2519,9 +2528,9 @@ function collectDevReady(): JsonValue {
|
||||
const sshSharedReady = existsSync("/root/.ssh") && sshKeyProbe.ok && sshKeyProbe.output.trim().length > 0;
|
||||
const skills = collectSkillsStatus() as Record<string, JsonValue>;
|
||||
const skillsSync = collectSkillsSyncPreflight() as Record<string, JsonValue>;
|
||||
const skillsReady = skills.ok === true;
|
||||
const skillsReady = skills.ok === true || skills.runnerUsable === true;
|
||||
const runtimePreflight = runtimePreflightJson(collectRuntimePreflight({ includeRemote: false, includePushDryRun: false }));
|
||||
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady && skillsReady && skillsSync.ok === true;
|
||||
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady && skillsReady;
|
||||
const value: JsonValue = {
|
||||
ok,
|
||||
missingTools,
|
||||
|
||||
@@ -8,7 +8,7 @@ export const codeQueueEnvironmentHintTitle = "# Code Queue 运行环境提示";
|
||||
export const codeQueueEnvironmentHint = [
|
||||
codeQueueEnvironmentHintTitle,
|
||||
"如果当前 Code Queue Docker 容器缺少完成任务所需的环境、系统包或语言依赖,可以先在容器内临时安装以推进当前任务;同时必须把该依赖补到 `src/components/microservices/code-queue/Dockerfile`,让后续任务重建镜像后可直接使用。",
|
||||
"任务可通过 `UNIDESK_SKILLS_PATH`(默认 `/root/.agents/skills`)读取注入的宿主 skills;若需要确认注入状态,查询 Code Queue `/api/dev-ready` 的 `devReady.skills`,缺失时报告该字段的修复建议,不要读取或输出宿主 token/auth 配置。",
|
||||
"任务可通过 `UNIDESK_SKILLS_PATH` 读取注入的宿主 skills;默认目标是 `/root/.agents/skills`,如果该挂载暂缺但 approved source `/home/ubuntu/.agents/skills` 可读,runner 会把 `UNIDESK_SKILLS_PATH` 解析到可用 source 并在 `/api/dev-ready` 的 `devReady.skills.resolution` 中报告需要 host rollout 的合同退化,不要读取或输出宿主 token/auth 配置。",
|
||||
].join("\n");
|
||||
|
||||
export function stripAutoReferenceHint(prompt: string): string {
|
||||
|
||||
@@ -656,7 +656,7 @@ export function collectRuntimePreflight(options: RuntimePreflightOptions = {}):
|
||||
};
|
||||
const pullRequestDelivery = collectPullRequestDeliveryPreflight(options, checkedAt);
|
||||
return {
|
||||
ok: skills.ok && skillsSync.ok && ports.codex.ok && ports.opencode.ok && pullRequestDelivery.ok,
|
||||
ok: skills.runnerUsable && ports.codex.ok && ports.opencode.ok && pullRequestDelivery.ok,
|
||||
checkedAt,
|
||||
cwd: process.cwd(),
|
||||
pid: process.pid,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { accessSync, constants, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { accessSync, constants, existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import type { JsonValue } from "./types";
|
||||
@@ -11,18 +11,53 @@ export interface SkillAvailabilityOptions {
|
||||
|
||||
export interface SkillAvailabilityReport {
|
||||
ok: boolean;
|
||||
runnerUsable: boolean;
|
||||
contractOk: boolean;
|
||||
available: boolean;
|
||||
degraded: boolean;
|
||||
blocker: string | null;
|
||||
degradedReason: string | null;
|
||||
checkedAt: string;
|
||||
source: string;
|
||||
target: string;
|
||||
path: string;
|
||||
resolvedPath: string;
|
||||
resolvedPathSource: "target" | "target-symlink" | "source-fallback" | "missing";
|
||||
resolution: {
|
||||
env: "UNIDESK_SKILLS_PATH";
|
||||
configuredTarget: string;
|
||||
configuredSource: string;
|
||||
resolvedPath: string;
|
||||
resolvedPathSource: "target" | "target-symlink" | "source-fallback" | "missing";
|
||||
runnerEnvValue: string;
|
||||
passesToRunnerEnv: true;
|
||||
targetBlocker: string | null;
|
||||
degradedReason: string | null;
|
||||
hostRolloutRequired: boolean;
|
||||
};
|
||||
mountPoint: string | null;
|
||||
exists: boolean;
|
||||
directory: boolean;
|
||||
readable: boolean;
|
||||
readonly: boolean;
|
||||
skillCount: number;
|
||||
targetExists: boolean;
|
||||
targetDirectory: boolean;
|
||||
targetReadable: boolean;
|
||||
targetReadonly: boolean;
|
||||
targetSkillCount: number;
|
||||
targetMissingSkills: string[];
|
||||
targetSymlink: boolean;
|
||||
targetRealPath: string | null;
|
||||
sourceExists: boolean;
|
||||
sourceDirectory: boolean;
|
||||
sourceReadable: boolean;
|
||||
sourceReadonly: boolean;
|
||||
sourceSkillCount: number;
|
||||
sourceMissingSkills: string[];
|
||||
sourceMountPoint: string | null;
|
||||
sourceSymlink: boolean;
|
||||
sourceRealPath: string | null;
|
||||
requiredSkills: string[];
|
||||
missingSkills: string[];
|
||||
skills: Array<{ name: string; present: boolean; skillMdPresent: boolean; path: string }>;
|
||||
@@ -49,6 +84,8 @@ export interface SkillSyncPathReport {
|
||||
writable: boolean;
|
||||
readonly: boolean;
|
||||
mountPoint: string | null;
|
||||
symlink: boolean;
|
||||
realPath: string | null;
|
||||
skillCount: number;
|
||||
requiredSkills: string[];
|
||||
missingSkills: string[];
|
||||
@@ -159,6 +196,10 @@ function normalizedPathText(path: string): string {
|
||||
return path.replace(/\\/gu, "/").replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
function normalizedResolvedPath(path: string | null): string | null {
|
||||
return path === null ? null : normalizedPathText(path);
|
||||
}
|
||||
|
||||
function isForbiddenSkillsPath(path: string): boolean {
|
||||
const normalized = normalizedPathText(path);
|
||||
return normalized === forbiddenRelativeSkillsPath
|
||||
@@ -235,6 +276,8 @@ function collectSyncPathReport(path: string, approved: boolean, requiredSkills:
|
||||
const exists = existsSync(path);
|
||||
const permissionFailures: SkillSyncPreflightReport["permissionFailures"] = [];
|
||||
let directory = false;
|
||||
let symlink = false;
|
||||
let realPath: string | null = null;
|
||||
let readable = false;
|
||||
let writable = false;
|
||||
let skillCount = 0;
|
||||
@@ -243,6 +286,13 @@ function collectSyncPathReport(path: string, approved: boolean, requiredSkills:
|
||||
|
||||
if (exists) {
|
||||
try {
|
||||
const lstat = lstatSync(path);
|
||||
symlink = lstat.isSymbolicLink();
|
||||
try {
|
||||
realPath = realpathSync(path);
|
||||
} catch {
|
||||
realPath = null;
|
||||
}
|
||||
const stat = statSync(path);
|
||||
directory = stat.isDirectory();
|
||||
} catch (probeError) {
|
||||
@@ -279,6 +329,8 @@ function collectSyncPathReport(path: string, approved: boolean, requiredSkills:
|
||||
writable,
|
||||
readonly: exists && directory && (mountInfo.readonly === true || !writable),
|
||||
mountPoint: mountInfo.mountPoint,
|
||||
symlink,
|
||||
realPath,
|
||||
skillCount,
|
||||
requiredSkills,
|
||||
missingSkills,
|
||||
@@ -289,73 +341,129 @@ function collectSyncPathReport(path: string, approved: boolean, requiredSkills:
|
||||
};
|
||||
}
|
||||
|
||||
function targetBlockerFor(report: SkillSyncPathReport): string | null {
|
||||
if (!report.exists) return "skills-target-missing";
|
||||
if (!report.directory) return "skills-target-not-directory";
|
||||
if (!report.readable) return "skills-target-permission-failed";
|
||||
if (!report.readonly) return "skills-target-not-readonly";
|
||||
if (report.missingSkills.length > 0) return "required-target-skills-missing";
|
||||
return null;
|
||||
}
|
||||
|
||||
function sourceBlockerFor(report: SkillSyncPathReport): string | null {
|
||||
if (!report.exists) return "skills-source-missing";
|
||||
if (!report.directory) return "skills-source-not-directory";
|
||||
if (!report.readable) return "skills-source-permission-failed";
|
||||
if (report.missingSkills.length > 0) return "required-source-skills-missing";
|
||||
return null;
|
||||
}
|
||||
|
||||
function reportHasRequiredSkills(report: SkillSyncPathReport): boolean {
|
||||
return report.exists && report.directory && report.readable && report.missingSkills.length === 0;
|
||||
}
|
||||
|
||||
function sameResolvedPath(left: string | null, right: string | null): boolean {
|
||||
const normalizedLeft = normalizedResolvedPath(left);
|
||||
const normalizedRight = normalizedResolvedPath(right);
|
||||
return normalizedLeft !== null && normalizedRight !== null && normalizedLeft === normalizedRight;
|
||||
}
|
||||
|
||||
export function collectSkillAvailability(options: SkillAvailabilityOptions): SkillAvailabilityReport {
|
||||
const target = options.target;
|
||||
const source = options.source ?? defaultSource;
|
||||
const requiredSkills = options.requiredSkills ?? defaultRequiredSkills;
|
||||
const checkedAt = new Date().toISOString();
|
||||
const mountInfo = mountInfoForPath(target);
|
||||
const exists = existsSync(target);
|
||||
const forbiddenSourceConfigured = isForbiddenSkillsPath(source);
|
||||
const forbiddenTargetConfigured = isForbiddenSkillsPath(target);
|
||||
const forbiddenPathConfigured = forbiddenSourceConfigured || forbiddenTargetConfigured;
|
||||
const forbiddenPathExists = forbiddenTargets.some((path) => existsSync(path));
|
||||
let directory = false;
|
||||
let skillCount = 0;
|
||||
let readonly = mountInfo.readonly === true;
|
||||
let error: string | null = null;
|
||||
let skills: SkillAvailabilityReport["skills"] = requiredSkills.map((name) => ({
|
||||
name,
|
||||
present: false,
|
||||
skillMdPresent: false,
|
||||
path: resolve(target, name),
|
||||
}));
|
||||
|
||||
if (exists) {
|
||||
try {
|
||||
const stat = statSync(target);
|
||||
directory = stat.isDirectory();
|
||||
if (directory) {
|
||||
const entries = readdirSync(target, { withFileTypes: true });
|
||||
skillCount = entries.filter((entry) => entry.isDirectory()).length;
|
||||
skills = skillStatus(target, requiredSkills);
|
||||
if (mountInfo.readonly === null) {
|
||||
readonly = commandOk("sh", ["-lc", `test ! -w ${shellQuote(target)}`]);
|
||||
}
|
||||
}
|
||||
} catch (probeError) {
|
||||
error = probeError instanceof Error ? probeError.message : String(probeError);
|
||||
}
|
||||
}
|
||||
|
||||
const missingSkills = skills.filter((skill) => !skill.present || !skill.skillMdPresent).map((skill) => skill.name);
|
||||
const available = exists && directory;
|
||||
const sourceProbe = collectSyncPathReport(source, source === defaultSource, requiredSkills).report;
|
||||
const targetProbe = collectSyncPathReport(target, target === expectedTarget || normalizedPathText(target) === normalizedPathText(source), requiredSkills).report;
|
||||
const targetSymlinkToSource = targetProbe.symlink && sameResolvedPath(targetProbe.realPath, sourceProbe.realPath);
|
||||
const targetHasRequiredSkills = reportHasRequiredSkills(targetProbe);
|
||||
const sourceHasRequiredSkills = reportHasRequiredSkills(sourceProbe);
|
||||
const targetReadonlyOk = targetProbe.readonly || targetSymlinkToSource || normalizedPathText(target) === normalizedPathText(source);
|
||||
const targetReady = targetHasRequiredSkills && targetReadonlyOk;
|
||||
const targetBlocker = targetHasRequiredSkills && targetSymlinkToSource ? null : targetBlockerFor(targetProbe);
|
||||
const sourceBlocker = sourceBlockerFor(sourceProbe);
|
||||
const missingBoth = !targetProbe.exists && !sourceProbe.exists;
|
||||
const resolutionSource: SkillAvailabilityReport["resolvedPathSource"] = forbiddenPathConfigured || missingBoth
|
||||
? "missing"
|
||||
: targetReady
|
||||
? targetSymlinkToSource ? "target-symlink" : "target"
|
||||
: sourceHasRequiredSkills
|
||||
? "source-fallback"
|
||||
: "missing";
|
||||
const resolvedPath = resolutionSource === "target" || resolutionSource === "target-symlink"
|
||||
? target
|
||||
: resolutionSource === "source-fallback"
|
||||
? source
|
||||
: target;
|
||||
const selectedReport = resolutionSource === "source-fallback" ? sourceProbe : targetProbe;
|
||||
const runnerUsable = !forbiddenPathConfigured && resolutionSource !== "missing";
|
||||
const contractOk = runnerUsable && resolutionSource !== "source-fallback" && targetReady && !forbiddenPathExists;
|
||||
const blocker = forbiddenPathConfigured
|
||||
? "forbidden-skills-path-configured"
|
||||
: !available
|
||||
? "skills-target-missing"
|
||||
: !readonly
|
||||
? "skills-target-not-readonly"
|
||||
: missingSkills.length > 0
|
||||
? "required-skills-missing"
|
||||
: forbiddenPathExists
|
||||
? "forbidden-skills-path-present"
|
||||
: null;
|
||||
const ok = blocker === null;
|
||||
: missingBoth
|
||||
? "skills-source-and-target-missing"
|
||||
: !runnerUsable
|
||||
? targetBlocker ?? sourceBlocker ?? "required-skills-missing"
|
||||
: contractOk
|
||||
? null
|
||||
: targetBlocker ?? (forbiddenPathExists ? "forbidden-skills-path-present" : null);
|
||||
const degradedReason = contractOk ? null : blocker;
|
||||
const ok = runnerUsable;
|
||||
const missingSkills = selectedReport.missingSkills;
|
||||
const skills = resolutionSource === "source-fallback" ? sourceProbe.skills : targetProbe.skills;
|
||||
return {
|
||||
ok,
|
||||
available,
|
||||
degraded: !ok,
|
||||
runnerUsable,
|
||||
contractOk,
|
||||
available: runnerUsable,
|
||||
degraded: !contractOk,
|
||||
blocker,
|
||||
degradedReason,
|
||||
checkedAt,
|
||||
source,
|
||||
target,
|
||||
path: target,
|
||||
mountPoint: mountInfo.mountPoint,
|
||||
exists,
|
||||
directory,
|
||||
readonly,
|
||||
skillCount,
|
||||
path: resolvedPath,
|
||||
resolvedPath,
|
||||
resolvedPathSource: resolutionSource,
|
||||
resolution: {
|
||||
env: "UNIDESK_SKILLS_PATH",
|
||||
configuredTarget: target,
|
||||
configuredSource: source,
|
||||
resolvedPath,
|
||||
resolvedPathSource: resolutionSource,
|
||||
runnerEnvValue: resolvedPath,
|
||||
passesToRunnerEnv: true,
|
||||
targetBlocker,
|
||||
degradedReason,
|
||||
hostRolloutRequired: runnerUsable && !contractOk,
|
||||
},
|
||||
mountPoint: selectedReport.mountPoint,
|
||||
exists: targetProbe.exists,
|
||||
directory: targetProbe.directory,
|
||||
readable: targetProbe.readable,
|
||||
readonly: targetProbe.readonly,
|
||||
skillCount: selectedReport.skillCount,
|
||||
targetExists: targetProbe.exists,
|
||||
targetDirectory: targetProbe.directory,
|
||||
targetReadable: targetProbe.readable,
|
||||
targetReadonly: targetProbe.readonly,
|
||||
targetSkillCount: targetProbe.skillCount,
|
||||
targetMissingSkills: targetProbe.missingSkills,
|
||||
targetSymlink: targetProbe.symlink,
|
||||
targetRealPath: targetProbe.realPath,
|
||||
sourceExists: sourceProbe.exists,
|
||||
sourceDirectory: sourceProbe.directory,
|
||||
sourceReadable: sourceProbe.readable,
|
||||
sourceReadonly: sourceProbe.readonly,
|
||||
sourceSkillCount: sourceProbe.skillCount,
|
||||
sourceMissingSkills: sourceProbe.missingSkills,
|
||||
sourceMountPoint: sourceProbe.mountPoint,
|
||||
sourceSymlink: sourceProbe.symlink,
|
||||
sourceRealPath: sourceProbe.realPath,
|
||||
requiredSkills,
|
||||
missingSkills,
|
||||
skills,
|
||||
@@ -371,10 +479,12 @@ export function collectSkillAvailability(options: SkillAvailabilityOptions): Ski
|
||||
forbiddenPathMustNotBeUsed: true,
|
||||
},
|
||||
expectedMount: `${defaultSource} mounted read-only to ${expectedTarget}`,
|
||||
repairHint: ok
|
||||
repairHint: contractOk
|
||||
? null
|
||||
: `Mount ${defaultSource} read-only at ${expectedTarget}, set UNIDESK_SKILLS_PATH=${expectedTarget}, and remove any forbidden skills path spelling.`,
|
||||
error,
|
||||
: runnerUsable
|
||||
? `Runner can use ${resolvedPath}; restore the read-only ${defaultSource} -> ${expectedTarget} projection so host rollout no longer reports ${degradedReason ?? "skills contract degraded"}.`
|
||||
: `Mount ${defaultSource} read-only at ${expectedTarget}, set UNIDESK_SKILLS_PATH=${expectedTarget}, and remove any forbidden skills path spelling.`,
|
||||
error: selectedReport.error,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ export interface RuntimeConfig {
|
||||
executionProviderIds: string[];
|
||||
remoteCodexEnvKeys: string[];
|
||||
skillsPath: string;
|
||||
resolvedRunnerSkillsPath: () => string;
|
||||
codexHome: string;
|
||||
opencodeXdgDir: string;
|
||||
sourceCodexConfig: string;
|
||||
|
||||
Reference in New Issue
Block a user