Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions registry/coder-labs/modules/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.0.0"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
workdir = "/home/coder/project"
Expand All @@ -33,7 +33,7 @@ module "codex" {
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.0.0"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
workdir = "/home/coder/project"
Expand Down Expand Up @@ -61,7 +61,7 @@ module "coder-login" {

module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.0.0"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
Expand All @@ -84,6 +84,7 @@ module "codex" {
- **System Prompt**: If `codex_system_prompt` is set, writes the prompt to `AGENTS.md` in the `~/.codex/` directory
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided)
- **Session Continuity**: When `continue = true` (default), the module automatically tracks task sessions in `~/.codex/.codex-task-session`. On workspace restart, it resumes the existing session with full conversation history. Set `continue = false` to always start fresh sessions.

## Configuration

Expand All @@ -107,7 +108,7 @@ For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_se
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.0.0"
version = "3.1.0"
# ... other variables ...

# Override default configuration
Expand Down
35 changes: 35 additions & 0 deletions registry/coder-labs/modules/codex/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,39 @@ describe("codex", async () => {
expect(prompt.exitCode).not.toBe(0);
expect(prompt.stderr).toContain("No such file or directory");
});

test("codex-continue-resume-existing-session", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
ai_prompt: "test prompt",
},
});

const workdir = "/home/coder";
const mockSessionId = "019a1234-5678-9abc-def0-123456789012";
const trackingFile = "/home/coder/.codex/.codex-task-session";

await execContainer(id, ["mkdir", "-p", "/home/coder/.codex"]);
await execContainer(id, [
"bash",
"-c",
`echo "${workdir}|${mockSessionId}" > ${trackingFile}`,
]);

await execModuleScript(id);

const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.codex-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain("Found existing task session");
expect(startLog.stdout).toContain(mockSessionId);
expect(startLog.stdout).toContain("Resuming existing session");
expect(startLog.stdout).toContain(
`Starting Codex with arguments: --model gpt-4-turbo resume ${mockSessionId}`,
);
expect(startLog.stdout).not.toContain("test prompt");
});
});
11 changes: 9 additions & 2 deletions registry/coder-labs/modules/codex/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ variable "ai_prompt" {
default = ""
}

variable "continue" {
type = bool
description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)."
default = true
}

variable "codex_system_prompt" {
type = string
description = "System instructions written to AGENTS.md in the ~/.codex directory"
Expand Down Expand Up @@ -187,8 +193,9 @@ module "agentapi" {
ARG_OPENAI_API_KEY='${var.openai_api_key}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_CODEX_MODEL='${var.codex_model}' \
ARG_CODEX_START_DIRECTORY='${var.workdir}' \
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
ARG_CODEX_TASK_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_CONTINUE='${var.continue}' \
/tmp/start.sh
EOT

Expand All @@ -206,7 +213,7 @@ module "agentapi" {
ARG_BASE_CONFIG_TOML='${base64encode(var.base_config_toml)}' \
ARG_ADDITIONAL_MCP_SERVERS='${base64encode(var.additional_mcp_servers)}' \
ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_CODEX_START_DIRECTORY='${var.workdir}' \
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
/tmp/install.sh
EOT
Expand Down
216 changes: 176 additions & 40 deletions registry/coder-labs/modules/codex/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
source "$HOME"/.bashrc
set -o errexit
set -o pipefail

command_exists() {
command -v "$1" > /dev/null 2>&1
}
Expand All @@ -16,60 +17,195 @@ fi
printf "Version: %s\n" "$(codex --version)"
set -o nounset
ARG_CODEX_TASK_PROMPT=$(echo -n "$ARG_CODEX_TASK_PROMPT" | base64 -d)
ARG_CONTINUE=${ARG_CONTINUE:-true}

echo "=== Codex Launch Configuration ==="
printf "OpenAI API Key: %s\n" "$([ -n "$ARG_OPENAI_API_KEY" ] && echo "Provided" || echo "Not provided")"
printf "Codex Model: %s\n" "${ARG_CODEX_MODEL:-"Default"}"
printf "Start Directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
printf "Has Task Prompt: %s\n" "$([ -n "$ARG_CODEX_TASK_PROMPT" ] && echo "Yes" || echo "No")"
printf "Report Tasks: %s\n" "$ARG_REPORT_TASKS"
printf "Continue Sessions: %s\n" "$ARG_CONTINUE"
echo "======================================"
set +o nounset
CODEX_ARGS=()

if command_exists codex; then
printf "Codex is installed\n"
else
printf "Error: Codex is not installed. Please enable install_codex or install it manually\n"
exit 1
fi
SESSION_TRACKING_FILE="$HOME/.codex/.codex-task-session"

if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
find_session_for_directory() {
local target_dir="$1"

if [ ! -f "$SESSION_TRACKING_FILE" ]; then
return 1
fi

local session_id=$(grep "^$target_dir|" "$SESSION_TRACKING_FILE" | cut -d'|' -f2 | head -1)

if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi

return 1
}

store_session_mapping() {
local dir="$1"
local session_id="$2"

mkdir -p "$(dirname "$SESSION_TRACKING_FILE")"

if [ -f "$SESSION_TRACKING_FILE" ]; then
grep -v "^$dir|" "$SESSION_TRACKING_FILE" > "$SESSION_TRACKING_FILE.tmp" 2> /dev/null || true
mv "$SESSION_TRACKING_FILE.tmp" "$SESSION_TRACKING_FILE"
fi

echo "$dir|$session_id" >> "$SESSION_TRACKING_FILE"
}

find_recent_session_file() {
local target_dir="$1"
local sessions_dir="$HOME/.codex/sessions"

if [ ! -d "$sessions_dir" ]; then
return 1
fi

local latest_file=""
local latest_time=0

while IFS= read -r session_file; do
local file_time=$(stat -c %Y "$session_file" 2> /dev/null || stat -f %m "$session_file" 2> /dev/null || echo "0")
local first_line=$(head -n 1 "$session_file" 2> /dev/null)
local session_cwd=$(echo "$first_line" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4)

if [ "$session_cwd" = "$target_dir" ] && [ "$file_time" -gt "$latest_time" ]; then
latest_file="$session_file"
latest_time="$file_time"
fi
done < <(find "$sessions_dir" -type f -name "*.jsonl" 2> /dev/null)

if [ -n "$latest_file" ]; then
local first_line=$(head -n 1 "$latest_file")
local session_id=$(echo "$first_line" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi
fi

return 1
}

wait_for_session_file() {
local target_dir="$1"
local max_attempts=20
local attempt=0

while [ $attempt -lt $max_attempts ]; do
local session_id=$(find_recent_session_file "$target_dir" 2> /dev/null || echo "")
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi
sleep 0.5
attempt=$((attempt + 1))
done

return 1
}

validate_codex_installation() {
if command_exists codex; then
printf "Codex is installed\n"
else
printf "Error: Codex is not installed. Please enable install_codex or install it manually\n"
exit 1
}
fi
fi
}

if [ -n "$ARG_CODEX_MODEL" ]; then
CODEX_ARGS+=("--model" "$ARG_CODEX_MODEL")
fi
setup_workdir() {
if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
fi
}

build_codex_args() {
CODEX_ARGS=()

if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
printf "Running the task prompt %s\n" "$ARG_CODEX_TASK_PROMPT"
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
if [ -n "$ARG_CODEX_MODEL" ]; then
CODEX_ARGS+=("--model" "$ARG_CODEX_MODEL")
fi

if [ "$ARG_CONTINUE" = "true" ]; then
existing_session=$(find_session_for_directory "$ARG_CODEX_START_DIRECTORY" 2> /dev/null || echo "")

if [ -n "$existing_session" ]; then
printf "Found existing task session for this directory: %s\n" "$existing_session"
printf "Resuming existing session...\n"
CODEX_ARGS+=("resume" "$existing_session")
else
printf "No existing task session found for this directory\n"
printf "Starting new task session...\n"

if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
fi
CODEX_ARGS+=("$PROMPT")
fi
fi
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
printf "Continue disabled, starting fresh session\n"

if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
fi
CODEX_ARGS+=("$PROMPT")
fi
fi
CODEX_ARGS+=("$PROMPT")
else
printf "No task prompt given.\n"
fi
}

capture_session_id() {
if [ "$ARG_CONTINUE" = "true" ] && [ -z "$existing_session" ]; then
printf "Capturing new session ID...\n"
new_session=$(wait_for_session_file "$ARG_CODEX_START_DIRECTORY" || echo "")

if [ -n "$new_session" ]; then
store_session_mapping "$ARG_CODEX_START_DIRECTORY" "$new_session"
printf "✓ Session tracked: %s\n" "$new_session"
printf "This session will be automatically resumed on next restart\n"
else
printf "⚠ Could not capture session ID after 10s timeout\n"
fi
fi
}

start_codex() {
printf "Starting Codex with arguments: %s\n" "${CODEX_ARGS[*]}"
agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}" &
capture_session_id
}

# Terminal dimensions optimized for Coder Tasks UI sidebar:
# - Width 67: fits comfortably in sidebar
# - Height 1190: adjusted due to Codex terminal height bug
printf "Starting Codex with arguments: %s\n" "${CODEX_ARGS[*]}"
agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}"
validate_codex_installation
setup_workdir
build_codex_args
start_codex