Add local Codex skill directory support

This commit is contained in:
Eduard Baturin 2026-04-20 23:00:38 +03:00
parent b4d869bd0d
commit 054e77a370
4 changed files with 33 additions and 3 deletions

View file

@ -3,6 +3,7 @@ BOT_API_ROOT=
BOT_CONNECTOR=local-codex
LOCAL_CODEX_COMMAND=codex
LOCAL_CODEX_WORKDIR=
LOCAL_CODEX_SKILL_DIR=
LOCAL_CODEX_SANDBOX=workspace-write
LOCAL_CODEX_MODEL=
LOCAL_CODEX_PROFILE=

View file

@ -22,6 +22,7 @@ BOT_API_ROOT=
BOT_CONNECTOR=local-codex
LOCAL_CODEX_COMMAND=codex
LOCAL_CODEX_WORKDIR=
LOCAL_CODEX_SKILL_DIR=
LOCAL_CODEX_SANDBOX=workspace-write
LOCAL_CODEX_MODEL=
LOCAL_CODEX_PROFILE=
@ -70,12 +71,15 @@ Relevant variables:
- `BOT_CONNECTOR=local-codex`
- `LOCAL_CODEX_COMMAND=codex`
- `LOCAL_CODEX_WORKDIR=`: working directory for Codex. Empty means this project root.
- `LOCAL_CODEX_SKILL_DIR=`: optional path to a local skill directory. `~` is supported.
- `LOCAL_CODEX_SANDBOX=workspace-write`: sandbox mode passed to `codex exec`
- `LOCAL_CODEX_MODEL=`: optional model override
- `LOCAL_CODEX_PROFILE=`: optional Codex profile
- `LOCAL_CODEX_SKIP_GIT_REPO_CHECK=`: set to `true` if the Codex workdir is not a git repo
- `LOCAL_CODEX_ADD_DIRS=`: comma-separated extra directories for Codex access
If `LOCAL_CODEX_SKILL_DIR` is set, the directory is passed to Codex as an accessible path and the agent is explicitly instructed to inspect and use that skill for media-generation tasks.
The connector queue is sequential, so messages are processed one by one in arrival order.
## Optional: Local Bot API Server For Files Larger Than 20 MB

View file

@ -1,3 +1,4 @@
const os = require("node:os");
const path = require("node:path");
const dotenv = require("dotenv");
@ -36,8 +37,20 @@ function readList(value) {
.filter(Boolean);
}
function expandHomePath(value) {
if (value === "~") {
return os.homedir();
}
if (typeof value === "string" && value.startsWith("~/")) {
return path.join(os.homedir(), value.slice(2));
}
return value;
}
function resolveFromRoot(rootDir, value, fallback) {
const resolvedValue = readString(value, fallback);
const resolvedValue = expandHomePath(readString(value, fallback));
if (!resolvedValue) {
return null;
@ -62,6 +75,7 @@ const connector = {
localCodex: {
command: readString(process.env.LOCAL_CODEX_COMMAND, "codex"),
workdir: resolveFromRoot(rootDir, process.env.LOCAL_CODEX_WORKDIR, rootDir),
skillDir: resolveFromRoot(rootDir, process.env.LOCAL_CODEX_SKILL_DIR),
sandbox: readString(process.env.LOCAL_CODEX_SANDBOX, "workspace-write"),
model: readString(process.env.LOCAL_CODEX_MODEL),
profile: readString(process.env.LOCAL_CODEX_PROFILE),

View file

@ -62,10 +62,18 @@ function formatAttachmentBlock(attachments) {
.join("\n");
}
function buildPrompt(request) {
function buildPrompt(request, config) {
const userText = request.text?.trim() || "(empty)";
const username = request.source.username || "(unknown)";
const displayName = request.source.displayName || "(unknown)";
const skillDirBlock = config.skillDir
? [
"",
"Local media skill:",
`- path: ${config.skillDir}`,
"- If the task requires generating or editing images, video, or other media, inspect this skill and use it.",
]
: [];
return [
"You are answering a Telegram user through a bot connector.",
@ -92,6 +100,7 @@ function buildPrompt(request) {
"",
"Attachments:",
formatAttachmentBlock(request.attachments),
...skillDirBlock,
].join("\n");
}
@ -127,6 +136,7 @@ function buildCodexArgs(config, request, outputPath) {
.map((attachment) => path.dirname(attachment.path))
.filter((attachmentDir) => !isInsideDirectory(config.workdir, attachmentDir));
const addDirs = dedupePaths([
config.skillDir,
...config.addDirs,
...attachmentDirs,
]);
@ -181,6 +191,7 @@ function inferAttachmentKind(filePath, rawKind) {
function getAllowedFileRoots(config, request) {
return dedupePaths([
config.workdir,
config.skillDir,
...config.addDirs,
...request.attachments.map((attachment) => path.dirname(attachment.path)),
]);
@ -295,7 +306,7 @@ class LocalCodexConnectorStrategy extends ConnectorStrategy {
"last-message.txt",
);
const args = buildCodexArgs(this.config, request, outputPath);
const prompt = buildPrompt(request);
const prompt = buildPrompt(request, this.config);
try {
await runCodex(this.config.command, args, this.config.workdir, prompt);