Bot CLI Codex Interface¶
Purpose¶
This note describes the runtime path between Telegram, the bot, and Codex CLI. It is intentionally short and focused on the actual implementation in this repo.
Request Flow¶
- A Telegram text message reaches
TelecodexApplication._execute_prompt()in telecodex/bot.py. - The bot checks that:
- no other run is active in the same chat;
- a project is selected for the chat;
- the currently selected Codex session is loaded if one exists.
- The bot creates
TelegramStreamEditorfrom telecodex/streaming.py and sends the initial live status message. - The bot starts
CodexRunner.run()from telecodex/runner.py.
How Codex CLI Is Started¶
- The base command comes from
CODEX_COMMAND. - The subprocess is started with
cwd = project_path. - For a new conversation the command shape is:
codex exec --json -- "<prompt>"
- For continuation of an existing session the command shape is:
codex exec resume --json <codex_session_id> -- "<prompt>"
- Prompt text is always appended after
--, so a prompt that starts with-cannot be parsed as a CLI flag.
How CLI Output Is Parsed¶
CodexRunner reads stdout line by line and feeds each line into CodexJsonEventParser.
The parser extracts three kinds of useful information:
progress: commentary or reasoning text for the temporary Telegram status block;assistant_delta/assistant_snapshot: assistant reply text that is being built incrementally;session:session_idreturned by Codex, which becomes the canonicalcodex_session_id.
Supported event sources include:
item.message.deltamessage.deltaresponse.output_text.deltaevent_msgresponse_itemitem.completed
Invalid JSON lines and unknown event types are ignored without crashing the run.
How Output Reaches Telegram¶
TelegramStreamEditor is the bridge from parsed Codex output to the chat.
During the run:
publish_status()stores short human-readable progress lines;publish_answer()stores the latest known assistant text;- a refresh loop periodically edits one live Telegram message;
- the header shows
Telecodex thinkingorTelecodex workingwith a spinner and elapsed seconds.
At the end:
stream.finish()replaces the live status message with the final reply;- if the final text is too long, the chat gets a preview and the full text is sent as
codex_output.md; - if Telegram message edits stall or fail, the stream layer falls back to sending a fresh status or final message so the chat does not stay stuck on an old timer.
Result Handling¶
After the subprocess ends, the bot:
- appends the full command and raw Codex output to
history/conversation<TelegramUserID>.log; - saves or updates the returned
codex_session_id; - chooses the final user-facing text from
assistant_text,display_text, or raw output-derived fallbacks; - sends result buttons such as
New sessionandSwitch project.
Voice Input¶
Voice messages follow the same CLI path after transcription:
- Telegram voice is downloaded.
- Deepgram transcribes it.
- The transcript is sent through the same
_execute_prompt()path as a regular text message.