Compare commits

...

2 Commits

Author SHA1 Message Date
35C4n0r 6c1613d1a9 feat(tasks): refactor boundary configuration and add wrapper path variable 2026-04-30 21:07:36 +05:30
35C4n0r c4931afbf4 feat(coder/modules/tasks): add tasks module 2026-04-22 00:32:27 +05:30
6 changed files with 525 additions and 256 deletions
@@ -1,256 +0,0 @@
#!/bin/bash
set -euo pipefail
ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"}
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH/#\~/$HOME}"
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}"
export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH"
command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_RESUME_SESSION_ID=${ARG_RESUME_SESSION_ID:-}
ARG_CONTINUE=${ARG_CONTINUE:-false}
ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-}
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d)
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"latest"}
ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false}
ARG_USE_BOUNDARY_DIRECTLY=${ARG_USE_BOUNDARY_DIRECTLY:-false}
ARG_CODER_HOST=${ARG_CODER_HOST:-}
echo "--------------------------------"
printf "ARG_RESUME: %s\n" "$ARG_RESUME_SESSION_ID"
printf "ARG_CONTINUE: %s\n" "$ARG_CONTINUE"
printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIONS"
printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE"
printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
printf "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE"
printf "ARG_USE_BOUNDARY_DIRECTLY: %s\n" "$ARG_USE_BOUNDARY_DIRECTLY"
printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"
echo "--------------------------------"
function install_boundary() {
if [ "$ARG_COMPILE_FROM_SOURCE" = "true" ]; then
# Install boundary by compiling from source
echo "Compiling boundary from source (version: $ARG_BOUNDARY_VERSION)"
echo "Removing existing boundary directory to allow re-running the script safely"
if [ -d boundary ]; then
rm -rf boundary
fi
echo "Clone boundary repository"
git clone https://github.com/coder/boundary.git
cd boundary
git checkout "$ARG_BOUNDARY_VERSION"
# Build the binary
make build
# Install binary
sudo cp boundary /usr/local/bin/
sudo chmod +x /usr/local/bin/boundary
elif [ "$ARG_USE_BOUNDARY_DIRECTLY" = "true" ]; then
# Install boundary using official install script
echo "Installing boundary using official install script (version: $ARG_BOUNDARY_VERSION)"
curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | bash -s -- --version "$ARG_BOUNDARY_VERSION"
else
# Use coder boundary subcommand (default) - no installation needed
echo "Using coder boundary subcommand (provided by Coder)"
fi
}
function validate_claude_installation() {
if command_exists claude; then
printf "Claude Code is installed\n"
else
printf "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
exit 1
fi
}
# Hardcoded task session ID for Coder task reporting
# This ensures all task sessions use a consistent, predictable ID
TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2"
get_project_dir() {
local workdir_normalized
workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/._' '-')
echo "$HOME/.claude/projects/${workdir_normalized}"
}
get_task_session_file() {
echo "$(get_project_dir)/${TASK_SESSION_ID}.jsonl"
}
task_session_exists() {
local session_file
session_file=$(get_task_session_file)
if [ -f "$session_file" ]; then
printf "Task session file found: %s\n" "$session_file"
return 0
else
printf "Task session file not found: %s\n" "$session_file"
return 1
fi
}
is_valid_session() {
local session_file="$1"
# Check if file exists and is not empty
# Empty files indicate the session was created but never used so they need to be removed
if [ ! -f "$session_file" ]; then
printf "Session validation failed: file does not exist\n"
return 1
fi
if [ ! -s "$session_file" ]; then
printf "Session validation failed: file is empty, removing stale file\n"
rm -f "$session_file"
return 1
fi
# Check for minimum session content
# Valid sessions need at least 2 lines: initial message and first response
local line_count
line_count=$(wc -l < "$session_file")
if [ "$line_count" -lt 2 ]; then
printf "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count"
rm -f "$session_file"
return 1
fi
# Validate JSONL format by checking first 3 lines
# Claude session files use JSONL (JSON Lines) format where each line is valid JSON
if ! head -3 "$session_file" | jq empty 2> /dev/null; then
printf "Session validation failed: invalid JSONL format, removing corrupt file\n"
rm -f "$session_file"
return 1
fi
# Verify the session has a valid sessionId field
# This ensures the file structure matches Claude's session format
if ! grep -q '"sessionId"' "$session_file" \
|| ! grep -m 1 '"sessionId"' "$session_file" | jq -e '.sessionId' > /dev/null 2>&1; then
printf "Session validation failed: no valid sessionId found, removing malformed file\n"
rm -f "$session_file"
return 1
fi
printf "Session validation passed: %s\n" "$session_file"
return 0
}
has_any_sessions() {
local project_dir
project_dir=$(get_project_dir)
if [ -d "$project_dir" ] && find "$project_dir" -maxdepth 1 -name "*.jsonl" -size +0c 2> /dev/null | grep -q .; then
printf "Sessions found in: %s\n" "$project_dir"
return 0
else
printf "No sessions found in: %s\n" "$project_dir"
return 1
fi
}
ARGS=()
function start_agentapi() {
# For Task reporting
export CODER_MCP_ALLOWED_TOOLS="coder_report_task"
mkdir -p "$ARG_WORKDIR"
cd "$ARG_WORKDIR"
if [ -n "$ARG_PERMISSION_MODE" ]; then
ARGS+=(--permission-mode "$ARG_PERMISSION_MODE")
fi
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
echo "Resuming specified session: $ARG_RESUME_SESSION_ID"
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
elif [ "$ARG_CONTINUE" = "true" ]; then
if [ "$ARG_REPORT_TASKS" = "true" ]; then
local session_file
session_file=$(get_task_session_file)
if task_session_exists && is_valid_session "$session_file"; then
echo "Resuming task session: $TASK_SESSION_ID"
ARGS+=(--resume "$TASK_SESSION_ID" --dangerously-skip-permissions)
else
echo "Starting new task session: $TASK_SESSION_ID"
ARGS+=(--session-id "$TASK_SESSION_ID" --dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
else
if has_any_sessions; then
echo "Continuing most recent standalone session"
ARGS+=(--continue)
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
else
echo "No sessions found, starting fresh standalone session"
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
fi
else
echo "Continue disabled, starting fresh session"
[ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ] && ARGS+=(--dangerously-skip-permissions)
[ -n "$ARG_AI_PROMPT" ] && ARGS+=(-- "$ARG_AI_PROMPT")
fi
printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"
if [ "$ARG_ENABLE_BOUNDARY" = "true" ]; then
install_boundary
printf "Starting with coder boundary enabled\n"
BOUNDARY_ARGS+=()
# Determine which boundary command to use
if [ "$ARG_COMPILE_FROM_SOURCE" = "true" ] || [ "$ARG_USE_BOUNDARY_DIRECTLY" = "true" ]; then
# Use boundary binary directly (from compilation or release installation)
BOUNDARY_CMD=("boundary")
else
# Use coder boundary subcommand (default)
# Copy coder binary to coder-no-caps. Copying strips CAP_NET_ADMIN capabilities
# from the binary, which is necessary because boundary doesn't work with
# privileged binaries (you can't launch privileged binaries inside network
# namespaces unless you have sys_admin).
CODER_NO_CAPS="$(dirname "$(which coder)")/coder-no-caps"
cp "$(which coder)" "$CODER_NO_CAPS"
BOUNDARY_CMD=("$CODER_NO_CAPS" "boundary")
fi
agentapi server --type claude --term-width 67 --term-height 1190 -- \
"${BOUNDARY_CMD[@]}" "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}"
else
agentapi server --type claude --term-width 67 --term-height 1190 -- claude "${ARGS[@]}"
fi
}
validate_claude_installation
start_agentapi
@@ -0,0 +1,21 @@
run "plan_with_required_vars" {
command = plan
variables {
agent_id = "example-agent-id"
}
}
run "app_url_uses_port" {
command = plan
variables {
agent_id = "example-agent-id"
port = 19999
}
assert {
condition = resource.coder_app.module_name.url == "http://localhost:19999"
error_message = "Expected module-name app URL to include configured port"
}
}
+71
View File
@@ -0,0 +1,71 @@
---
display_name: tasks
description: Describe what this module does
icon: ../../../../.icons/<A_RELEVANT_ICON>.svg
verified: false
tags: [helper]
---
# tasks
<!-- Describes what this module does -->
```tf
module "tasks" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/NAMESPACE/tasks/coder"
version = "1.0.0"
}
```
<!-- Add a screencast or screenshot here put them in .images directory -->
## Examples
### Example 1
Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
```tf
module "tasks" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/NAMESPACE/tasks/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
extensions = [
"dracula-theme.theme-dracula"
]
}
```
Enter the `<author>.<name>` into the extensions array and code-server will automatically install on start.
### Example 2
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settingsjson) file:
```tf
module "tasks" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/NAMESPACE/tasks/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
"workbench.colorTheme" = "Dracula"
}
}
```
### Example 3
Run code-server in the background, don't fetch it from GitHub:
```tf
module "tasks" {
source = "registry.coder.com/NAMESPACE/tasks/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
offline = true
}
```
+205
View File
@@ -0,0 +1,205 @@
terraform {
required_version = ">= 1.9"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
aap = {
source = "ansible/aap"
version = "1.3.0"
}
}
}
locals {
# A built-in icon like "/icon/code.svg" or a full URL of icon
icon_url = "https://raw.githubusercontent.com/coder/coder/main/site/static/icon/code.svg"
# a map of all possible values
options = {
"Option 1" = {
"name" = "Option 1",
"value" = "1"
"icon" = "/emojis/1.png"
}
"Option 2" = {
"name" = "Option 2",
"value" = "2"
"icon" = "/emojis/2.png"
}
}
}
# Add required variables for your modules and remove any unneeded variables
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "agent_module_ref" {
type = object({
agent_ref = string
agent_module_dir = string
agent_binary_path = string
})
}
variable "agent_parameters" {
type = object({
claude_code = optional(object({
resume_session_id = optional(string, "")
continue = optional(bool, false)
dangerously_skip_permissions = optional(bool, false)
permission_mode = optional(string, "")
}), null)
another_agent = optional(object({
temperature = optional(number, null)
system_prompt = optional(string, null)
}), null)
})
default = {}
validation {
condition = var.agent_parameters.claude_code == null || var.agent_module_ref.agent_name == "claude_code"
error_message = "'claude_code' parameters are only valid when ref is 'claude-code'."
}
validation {
condition = var.agent_parameters.another_agent == null || var.agent_module_ref.agent_name == "another_agent"
error_message = "'another_agent' parameters are only valid when ref is 'another_agent'."
}
}
variable "enable_agentapi" {
type = bool
description = "Whether to enable AgentAPI for this agent. If false, the AgentAPI module will not be included, the start script will still run and a cli app will be created which runs the agent in normal terminal mode"
}
variable "agentapi" {
description = <<-EOT
AgentAPI app configuration:
- `web_app`: Whether to create the web app for Claude Code. When false, AgentAPI still runs but no web UI app icon is shown in the Coder dashboard. This is automatically enabled when using Coder Tasks, regardless of this setting.
- `cli_app`: Whether to create a CLI app for Claude Code.
- `web_app_display_name`: Display name for the web app.
- `cli_app_display_name`: Display name for the CLI app.
- `web_app_icon`: The icon to use for the app.
EOT
type = object({
version = optional(string, "latest")
web_app = optional(bool, true)
cli_app = optional(bool, false)
web_app_display_name = optional(string, "ClaudeCode")
cli_app_display_name = optional(string, "ClaudeCode CLI")
web_app_icon = optional(string, "/icon/claude.svg")
module_directory = optional(string)
})
default = {}
}
variable "cli_app_display_name" {
type = string
description = "Display name for the CLI app. Only applicable if `enable_agentapi` is false."
default = "Agent CLI"
validation {
condition = var.enable_agentapi == false
error_message = "cli_app_display_name should not be set when enable_agentapi is true."
}
}
variable "enable_boundary" {
type = bool
description = "Whether to enable Boundary for this agent. If false, the Boundary module will not be included and Boundary will not be installed, but the start script will still run."
default = false
}
variable "boundary_wrapper_path" {
type = string
description = "Path to the Boundary CLI wrapper script. Only applicable if `enable_boundary` is true."
default = ""
validation {
condition = var.enable_boundary == false || (var.enable_boundary == true && var.boundary_wrapper_path != "")
error_message = "boundary_wrapper_path must be set when enable_boundary is true."
}
}
locals {
start_script = file("${path.module}/${var.agent_module_ref.agent_name}_start.sh")
export_variable_prefix = upper(var.agent_module_ref.agent_name)
export_variables = {
for key, value in var.agent_parameters[var.agent_module_ref.agent_name] : "${local.export_variable_prefix}_${upper(key)}" => value
}
export_merged_variables = merge(local.export_variables, {
"ARG_ENABLE_AGENTAPI" = var.enable_agentapi
"ARG_ENABLE_BOUNDARY" = var.enable_boundary
"ARG_BOUNDARY_WRAPPER_PATH" = var.boundary_wrapper_path
})
default_app_slugs = {
"claude_code" = "ccw"
}
app_slug = lookup(local.default_app_slugs, var.agent_module_ref.agent_name)
cli_app_slug = "${local.app_slug}-cli"
}
variable "ai_prompt" {
type = string
description = "Initial task prompt for Claude Code."
default = ""
}
resource "coder_script" "start_script" {
agent_id = var.agent_id
display_name = "Task Statrt Script"
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
echo -n '${base64encode(local.start_script)}' | base64 -d > "${var.agent_module_ref.agent_module_dir}/start.sh"
# Export variables for the script based on the provided agent parameters
%{for var_name, var_value in local.export_merged_variables~}
export ${var_name}="${var_value}"
%{endfor~}
chmod +x "${var.agent_module_ref.agent_module_dir}/start.sh"
"${var.agent_module_ref.agent_module_dir}/start.sh"
EOT
}
module "agentapi" {
count = var.enable_agentapi ? 1 : 0
source = "git::https://github.com/coder/registry.git//registry/coder/modules/agentapi?ref=35C4n0r/refactor-agentapi-decouple"
agentapi_version = var.agentapi.version
agent_id = var.agent_id
cli_app = var.agentapi.cli_app
cli_app_display_name = var.agentapi.cli_app_display_name
cli_app_slug = local.cli_app_slug
web_app = var.agentapi.web_app
web_app_display_name = var.agentapi.web_app_display_name
web_app_icon = var.agentapi.web_app_icon
web_app_slug = local.app_slug
module_directory = var.agentapi.module_directory
}
resource "coder_app" "non_agentapi_cli" {
count = var.enable_agentapi ? 0 : 1
agent_id = var.agent_id
display_name = var.cli_app_display_name
command = ""
slug = local.cli_app_slug
}
output "task_app_id" {
description = "The app ID for the task's web app, if created."
value = try(module.agentapi[0].task_app_id, coder_app.non_agentapi_cli[0].id)
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env sh
# Convert templated variables to shell variables
# shellcheck disable=SC2269
LOG_PATH=${LOG_PATH}
# shellcheck disable=SC2034
BOLD='\033[0;1m'
# shellcheck disable=SC2059
printf "$${BOLD}Installing MODULE_NAME ...\n\n"
# Add code here
# Use variables from the templatefile function in main.tf
# e.g. LOG_PATH, PORT, etc.
printf "🥳 Installation complete!\n\n"
printf "👷 Starting MODULE_NAME in background...\n\n"
# Start the app in here
# 1. Use & to run it in background
# 2. redirct stdout and stderr to log files
./app > "$${LOG_PATH}" 2>&1 &
printf "check logs at %s\n\n" "$${LOG_PATH}"
@@ -0,0 +1,202 @@
#!/bin/bash
set -euo pipefail
ARG_CLAUDE_BINARY_PATH=${CLAUDE_CODE_ARG_CLAUDE_BINARY_PATH:-"${HOME}/.local/bin"}
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH/#\~/${HOME}}"
ARG_CLAUDE_BINARY_PATH="${ARG_CLAUDE_BINARY_PATH//\$HOME/${HOME}}"
export PATH="${ARG_CLAUDE_BINARY_PATH}:${PATH}"
command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_RESUME_SESSION_ID=${CLAUDE_CODE_ARG_RESUME_SESSION_ID:-}
ARG_CONTINUE=${CLAUDE_CODE_ARG_CONTINUE:-false}
ARG_DANGEROUSLY_SKIP_PERMISSIONS=${CLAUDE_CODE_ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
ARG_PERMISSION_MODE=${CLAUDE_CODE_ARG_PERMISSION_MODE:-}
ARG_WORKDIR=${CLAUDE_CODE_ARG_WORKDIR:-"${HOME}"}
ARG_AI_PROMPT=$(echo -n "${CLAUDE_CODE_ARG_AI_PROMPT:-}" | base64 -d)
ARG_CODER_HOST=${CLAUDE_CODE_ARG_CODER_HOST:-}
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
ARG_ENABLE_AGENTAPI=${ARG_ENABLE_AGENTAPI:-false}
echo "--------------------------------"
printf "ARG_RESUME: %s\n" "${ARG_RESUME_SESSION_ID}"
printf "ARG_CONTINUE: %s\n" "${ARG_CONTINUE}"
printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "${ARG_DANGEROUSLY_SKIP_PERMISSIONS}"
printf "ARG_PERMISSION_MODE: %s\n" "${ARG_PERMISSION_MODE}"
printf "ARG_AI_PROMPT: %s\n" "${ARG_AI_PROMPT}"
printf "ARG_WORKDIR: %s\n" "${ARG_WORKDIR}"
printf "ARG_ENABLE_BOUNDARY: %s\n" "${ARG_ENABLE_BOUNDARY}"
printf "ARG_ENABLE_AGENTAPI: %s\n" "${ARG_ENABLE_AGENTAPI}"
printf "ARG_CODER_HOST: %s\n" "${ARG_CODER_HOST}"
echo "--------------------------------"
function validate_claude_installation() {
if command_exists claude; then
printf "Claude Code is installed\n"
else
printf "Error: Claude Code is not installed. Please enable install_claude_code or install it manually\n"
exit 1
fi
}
# Hardcoded task session ID for Coder task reporting
# This ensures all task sessions use a consistent, predictable ID
TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2"
get_project_dir() {
local workdir_normalized
workdir_normalized=$(echo "${ARG_WORKDIR}" | tr '/._' '-')
echo "${HOME}/.claude/projects/${workdir_normalized}"
}
get_task_session_file() {
echo "$(get_project_dir)/${TASK_SESSION_ID}.jsonl"
}
task_session_exists() {
local session_file
session_file=$(get_task_session_file)
if [ -f "$session_file" ]; then
printf "Task session file found: %s\n" "$session_file"
return 0
else
printf "Task session file not found: %s\n" "$session_file"
return 1
fi
}
is_valid_session() {
local session_file="$1"
# Check if file exists and is not empty
# Empty files indicate the session was created but never used so they need to be removed
if [ ! -f "$session_file" ]; then
printf "Session validation failed: file does not exist\n"
return 1
fi
if [ ! -s "$session_file" ]; then
printf "Session validation failed: file is empty, removing stale file\n"
rm -f "$session_file"
return 1
fi
# Check for minimum session content
# Valid sessions need at least 2 lines: initial message and first response
local line_count
line_count=$(wc -l < "$session_file")
if [ "$line_count" -lt 2 ]; then
printf "Session validation failed: incomplete (only %s lines), removing incomplete file\n" "$line_count"
rm -f "$session_file"
return 1
fi
# Validate JSONL format by checking first 3 lines
# Claude session files use JSONL (JSON Lines) format where each line is valid JSON
if ! head -3 "$session_file" | jq empty 2> /dev/null; then
printf "Session validation failed: invalid JSONL format, removing corrupt file\n"
rm -f "$session_file"
return 1
fi
# Verify the session has a valid sessionId field
# This ensures the file structure matches Claude's session format
if ! grep -q '"sessionId"' "$session_file" \
|| ! grep -m 1 '"sessionId"' "$session_file" | jq -e '.sessionId' > /dev/null 2>&1; then
printf "Session validation failed: no valid sessionId found, removing malformed file\n"
rm -f "$session_file"
return 1
fi
printf "Session validation passed: %s\n" "$session_file"
return 0
}
has_any_sessions() {
local project_dir
project_dir=$(get_project_dir)
if [ -d "$project_dir" ] && find "$project_dir" -maxdepth 1 -name "*.jsonl" -size +0c 2> /dev/null | grep -q .; then
printf "Sessions found in: %s\n" "$project_dir"
return 0
else
printf "No sessions found in: %s\n" "$project_dir"
return 1
fi
}
AGENTAPI_CMD=()
BOUNDARY_CMD=("${ARG_BOUNDARY_WRAPPER_PATH}" --)
AGENT_CMD=()
build_agentapi_cmd() {
AGENTAPI_CMD=(agentapi server --type claude --term-width 67 --term-height 1190 --)
}
build_agent_cmd() {
local args=()
if [[ -n "${ARG_PERMISSION_MODE}" ]]; then
args+=(--permission-mode "${ARG_PERMISSION_MODE}")
fi
if [[ -n "${ARG_RESUME_SESSION_ID}" ]]; then
echo "Resuming specified session: ${ARG_RESUME_SESSION_ID}"
args+=(--resume "${ARG_RESUME_SESSION_ID}")
[[ "${ARG_DANGEROUSLY_SKIP_PERMISSIONS}" = "true" ]] && args+=(--dangerously-skip-permissions)
elif [[ "${ARG_CONTINUE}" = "true" ]]; then
local session_file
session_file=$(get_task_session_file)
if task_session_exists && is_valid_session "${session_file}"; then
echo "Resuming task session: ${TASK_SESSION_ID}"
args+=(--resume "${TASK_SESSION_ID}" --dangerously-skip-permissions)
else
echo "Starting new task session: ${TASK_SESSION_ID}"
args+=(--session-id "${TASK_SESSION_ID}" --dangerously-skip-permissions)
[[ -n "${ARG_AI_PROMPT}" ]] && args+=(-- "${ARG_AI_PROMPT}")
fi
else
echo "Continue disabled, starting fresh session"
[[ "${ARG_DANGEROUSLY_SKIP_PERMISSIONS}" = "true" ]] && args+=(--dangerously-skip-permissions)
[[ -n "${ARG_AI_PROMPT}" ]] && args+=(-- "${ARG_AI_PROMPT}")
fi
AGENT_CMD=(claude "${args[@]}")
}
function start() {
# For Task reporting
export CODER_MCP_ALLOWED_TOOLS="coder_report_task"
mkdir -p "${ARG_WORKDIR}"
cd "${ARG_WORKDIR}"
if [[ "${ARG_ENABLE_AGENTAPI}" == "true" ]]; then
build_agentapi_cmd
fi
build_agent_cmd
printf "Running claude code with args: %s\n" "$(printf '%q ' "${AGENT_CMD[@]}")"
if [[ "${ARG_ENABLE_BOUNDARY}" = "true" ]]; then
printf "Starting with coder boundary enabled\n"
"${AGENTAPI_CMD[@]}" "" -- "${AGENT_CMD[@]}"
else
"${AGENTAPI_CMD[@]}" "${AGENT_CMD[@]}"
fi
}
validate_claude_installation
start