diff --git a/.env.example b/.env.example index f9ea631..4de7c47 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/setup.md b/setup.md index e9b1c22..8c8f21e 100644 --- a/setup.md +++ b/setup.md @@ -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 diff --git a/src/config.js b/src/config.js index 34f5e7f..d067f19 100644 --- a/src/config.js +++ b/src/config.js @@ -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), diff --git a/src/connectors/local-codex.js b/src/connectors/local-codex.js index 9140704..85e9229 100644 --- a/src/connectors/local-codex.js +++ b/src/connectors/local-codex.js @@ -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);