Merge 5b8332e88d6199eda9aa88e5ff09e656aa72f943 into 598f1826d8b2bc969aace2c6459824737667218c

This commit is contained in:
Kollerro 2026-03-21 11:20:02 +08:00 committed by GitHub
commit c5051b8bd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 940 additions and 0 deletions

View File

@ -0,0 +1,181 @@
---
name: claude-codex-delegation
description: Delegate code-writing, document generation, and analysis tasks to Claude Code or OpenAI Codex sub-processes. Use when you need to spawn a coding agent for file generation, code modification, script writing, or any multi-step coding task. Triggers on "delegate to claude", "delegate to codex", "use claude code", "use codex", "spawn coding agent", "run delegation", or when a task requires writing/modifying code that should be handed off to a sub-process.
metadata:
{ "openclaw": { "os": ["linux"], "requires": { "anyBins": ["claude", "codex"], "bins": ["bash", "timeout"] } } }
---
# Claude Code + Codex Delegation
Delegate coding tasks to Claude Code or OpenAI Codex as sub-processes. Each delegation is self-contained — the sub-process has no access to the parent agent's conversation, tools, or MCP servers. It does have full filesystem access within the working directory you specify.
This skill extends the built-in `coding-agent` skill with concrete delegation scripts, tmux session management, and an example delegation policy. Use `coding-agent` for prompt composition guidance; use this skill when you need the actual execution infrastructure.
## Installation
```bash
# Install everything (Claude Code, Codex, scripts, skill file)
./install.sh
# Already have Claude Code and Codex? Just install scripts
./install.sh --skip-npm
# Preview what will happen
./install.sh --dry-run
```
Requires Node.js 22+, an existing OpenClaw installation, and subscription/OAuth auth for both Claude Code and Codex (API key auth will not work — the scripts strip API keys by design).
## Agent Selection
| Request | Agent | CLI |
|---------|-------|-----|
| Default / unspecified | Claude Code | `claude` |
| User says "use codex" | Codex | `codex` |
| User says "use claude code" | Claude Code | `claude` |
Never auto-select Codex. Only use it when the user explicitly requests it.
## Quick Delegation (One-Shot)
```bash
# Claude Code (no PTY needed)
cd /path/to/project && claude --permission-mode bypassPermissions --print 'Your task prompt'
# Codex (PTY required)
bash pty:true workdir:/path/to/project command:"codex exec --full-auto 'Your task prompt'"
```
## Delegation via Script
Use `scripts/delegate.sh` for structured delegation with logging:
```bash
# Claude Code (default)
scripts/delegate.sh --prompt "Build a REST API for todos" --workdir ~/project
# Codex
scripts/delegate.sh --agent codex --prompt "Refactor the auth module" --workdir ~/project
# From a prompt file
scripts/delegate.sh --file /tmp/task-prompt.md --workdir ~/project --log /tmp/task.log
# Background mode (returns immediately)
scripts/delegate.sh --background --prompt "Long running task" --workdir ~/project
```
## Long-Running Tasks in tmux
Use `scripts/tmux-session.sh` for persistent sessions (requires tmux):
```bash
# Start a delegation in a named tmux session
scripts/tmux-session.sh --name build-api --agent claude --workdir ~/project \
--prompt "Build a REST API with authentication"
# Check session status
scripts/tmux-session.sh --status build-api
# Reattach to session
scripts/tmux-session.sh --attach build-api
# List all delegation sessions
scripts/tmux-session.sh --list
```
## Composing Self-Contained Prompts
The sub-process has no access to the parent agent's conversation, tools, or MCP servers. It can read and write files in the working directory, but has no other context. Include everything else it needs:
```markdown
# Task: [description]
## Context
[All relevant background — the sub-process has no conversation history]
## Verified Facts (use ONLY these)
[Every fact needed — do not assume the sub-process knows anything beyond the working directory]
## Anti-Hallucination Rule
Do not invent facts, numbers, names, or data not listed above.
If information is missing, use [TBD] as placeholder.
## Requirements
[Specific deliverable requirements — structure, format, audience]
## Output
[File format, naming, save location]
```
## Security: API Key Stripping
The delegation scripts strip 23 known AI provider API keys from the sub-process environment. This forces subscription/OAuth-based auth and prevents key leakage to child processes. The list is not exhaustive — if your environment includes provider keys not listed below, add them to the strip list in both scripts.
Keys stripped: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `GEMINI_API_KEY`, `AZURE_OPENAI_API_KEY`, `COHERE_API_KEY`, `MISTRAL_API_KEY`, `OPENROUTER_API_KEY`, `DEEPSEEK_API_KEY`, `TOGETHER_API_KEY`, `FIREWORKS_API_KEY`, `GROQ_API_KEY`, `PERPLEXITY_API_KEY`, `BRAVE_API_KEY`, `BRAVE_SEARCH_API_KEY`, `REPLICATE_API_TOKEN`, `AI21_API_KEY`, `HUGGINGFACE_API_KEY`, `HF_TOKEN`, `VOYAGE_API_KEY`, `ANYSCALE_API_KEY`, `XAI_API_KEY`.
This is critical when running sub-processes that might:
- Spawn their own child processes
- Log environment variables
- Send telemetry containing env vars
Both `delegate.sh` and `tmux-session.sh` handle this automatically. For manual invocations, strip at minimum:
```bash
env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY claude --permission-mode bypassPermissions --print 'task'
```
## ACP Runtime (If Available)
When OpenClaw's ACP runtime is configured, use it as the primary delegation path:
```
sessions_spawn with:
runtime: "acp"
agentId: "claude" # or "codex" if explicitly requested
task: "full prompt" # self-contained (same rules as above)
thread: true
mode: "session"
```
Fall back to `delegate.sh` if ACP is unhealthy.
## Waiting for Results
When running in background mode, always poll for completion:
1. Launch the delegation
2. Poll with `process action:poll sessionId:XXX` or check the log file
3. Verify the output exists (`ls -lh /path/to/expected/output`)
4. Deliver the result to the user
Never end your turn after step 1 without polling. The user will not see the result otherwise.
## Session Resumption
Claude Code sessions are persistent. For follow-up amendments:
```bash
# Resume the most recent session in the working directory
cd /working/dir && claude --permission-mode bypassPermissions --print --continue "Make these changes: ..."
# Resume a specific session by ID
claude --permission-mode bypassPermissions --print --resume <session-id> "Amendments: ..."
```
## Resource Limits
- Each Claude Code process uses 300-400 MB RAM
- Avoid running more than 2-3 concurrent delegations on machines with less than 8 GB RAM
- Codex requires a git repository — use `mktemp -d && cd $_ && git init` for scratch work
## Delegation Policy
See `references/delegation-policy.md` for a complete example policy covering when to delegate, prompt composition, security, and result handling. Adopt or adapt it for your deployment.
## Rules
1. **Respect agent choice** — if the user asks for Codex, use Codex
2. **Never hand-code patches yourself** when orchestrating — delegate or ask the user
3. **Be patient** — do not kill sessions because they appear slow
4. **Monitor with logs** — check progress without interfering
5. **Never start coding agents in the OpenClaw config directory** — they may read and act on internal config

View File

@ -0,0 +1,271 @@
#!/usr/bin/env bash
# install.sh - Install Claude Code + Codex delegation bundle for OpenClaw.
#
# Copies the entire skill directory (SKILL.md, scripts, references) into
# OpenClaw's shared skill location so the agent can discover and use it.
# Optionally installs the Claude Code and Codex CLIs via npm.
#
# Usage:
# install.sh [options]
#
# Options:
# --skip-npm Skip npm package installation (if already installed)
# --skill-dir DIR Override skill install location
# (default: ~/.openclaw/skills/claude-codex-delegation)
# --force Reinstall npm packages even if already present
# --dry-run Show what would be done without doing it
# -h, --help Show this help message
#
# Requirements:
# - Node.js 22+ with npm
# - Existing OpenClaw installation
# - GNU coreutils (timeout, script) on Linux; macOS needs coreutils via brew
# - Claude Max or OpenAI subscription (for subscription-based auth)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="${HOME}/.openclaw/skills/claude-codex-delegation"
SKIP_NPM=false
FORCE=false
DRY_RUN=false
# --- Pinned versions ---
CLAUDE_CODE_PKG="@anthropic-ai/claude-code@latest"
CODEX_PKG="@openai/codex@latest"
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
info() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--skill-dir)
if [[ $# -lt 2 ]]; then
echo "Error: $1 requires a value" >&2
exit 1
fi
SKILL_DIR="$2"; shift 2 ;;
--skip-npm) SKIP_NPM=true; shift ;;
--force) FORCE=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
-h|--help)
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
exit 0
;;
*) echo "Error: Unknown option: $1" >&2; exit 1 ;;
esac
done
run() {
if $DRY_RUN; then
echo " [dry-run] $*"
else
"$@"
fi
}
echo ""
echo "Claude Code + Codex Delegation - Installer"
echo "============================================"
echo ""
# --- Check Node.js ---
if ! command -v node &> /dev/null; then
error "Node.js is not installed. Install Node.js 22+ first."
echo " https://nodejs.org/ or: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
exit 1
fi
NODE_MAJOR="$(node -v | sed 's/^v//' | cut -d. -f1)"
if [[ "$NODE_MAJOR" -lt 22 ]]; then
error "Node.js $(node -v) detected. Version 22+ is required."
echo " Install Node.js 22+: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"
exit 1
fi
info "Node.js $(node -v)"
if ! command -v npm &> /dev/null; then
error "npm is not installed."
exit 1
fi
# --- Check OpenClaw ---
if ! command -v openclaw &> /dev/null; then
error "OpenClaw is not installed. Install with: npm install -g openclaw"
exit 1
fi
info "OpenClaw detected ($(openclaw --version 2>/dev/null || echo 'unknown version'))"
# --- Check required system utilities ---
echo ""
echo "Checking system dependencies..."
MISSING=0
if ! command -v timeout &> /dev/null; then
error "'timeout' is required but not found."
if [[ "$(uname)" == "Darwin" ]]; then
echo " Install with: brew install coreutils"
fi
MISSING=$((MISSING + 1))
else
info "timeout: available"
fi
if ! command -v script &> /dev/null; then
error "'script' (from util-linux) is required for Codex PTY support."
if [[ "$(uname)" == "Darwin" ]]; then
echo " Install with: brew install util-linux"
fi
MISSING=$((MISSING + 1))
else
info "script: available"
fi
if command -v tmux &> /dev/null; then
info "tmux: available"
else
warn "tmux: not installed (optional, needed for tmux-session.sh)"
fi
if [[ $MISSING -gt 0 ]]; then
error "Missing $MISSING required system dependencies. Install them and rerun."
exit 1
fi
# --- Install Claude Code and Codex ---
if ! $SKIP_NPM; then
echo ""
echo "Installing Claude Code..."
if command -v claude &> /dev/null && ! $FORCE; then
info "Claude Code already installed ($(claude --version 2>/dev/null || echo 'unknown version')). Use --force to reinstall."
else
run npm install -g "$CLAUDE_CODE_PKG"
if ! $DRY_RUN; then
info "Claude Code installed"
fi
fi
echo ""
echo "Installing OpenAI Codex CLI..."
if command -v codex &> /dev/null && ! $FORCE; then
info "Codex CLI already installed ($(codex --version 2>/dev/null || echo 'unknown version')). Use --force to reinstall."
else
run npm install -g "$CODEX_PKG"
if ! $DRY_RUN; then
info "Codex CLI installed"
fi
fi
fi
# --- Install skill bundle ---
# Copies the entire skill directory (SKILL.md, scripts/, references/) into
# OpenClaw's shared skill location (~/.openclaw/skills/) so the agent
# discovers it automatically. All resources stay inside the skill folder.
echo ""
echo "Installing skill bundle to $SKILL_DIR..."
run mkdir -p "$SKILL_DIR/scripts"
run mkdir -p "$SKILL_DIR/references"
# SKILL.md
if [[ -f "$SCRIPT_DIR/SKILL.md" ]]; then
run cp "$SCRIPT_DIR/SKILL.md" "$SKILL_DIR/SKILL.md"
info "Installed SKILL.md"
fi
# Scripts (inside the skill directory, not a global scripts dir)
for script in delegate.sh tmux-session.sh; do
SRC="$SCRIPT_DIR/scripts/$script"
DST="$SKILL_DIR/scripts/$script"
if [[ -f "$SRC" ]]; then
run cp "$SRC" "$DST"
run chmod +x "$DST"
info "Installed scripts/$script"
else
error "Script not found: $SRC"
fi
done
# References
if [[ -f "$SCRIPT_DIR/references/delegation-policy.md" ]]; then
run cp "$SCRIPT_DIR/references/delegation-policy.md" "$SKILL_DIR/references/delegation-policy.md"
info "Installed references/delegation-policy.md"
fi
# --- Verify ---
echo ""
echo "Verifying installation..."
ERRORS=0
if command -v claude &> /dev/null || $DRY_RUN; then
info "claude CLI: OK"
else
error "claude CLI: not found"
ERRORS=$((ERRORS + 1))
fi
if command -v codex &> /dev/null || $DRY_RUN; then
info "codex CLI: OK"
else
error "codex CLI: not found"
ERRORS=$((ERRORS + 1))
fi
if [[ -f "$SKILL_DIR/SKILL.md" ]] || $DRY_RUN; then
info "SKILL.md: OK"
else
error "SKILL.md: not found at $SKILL_DIR"
ERRORS=$((ERRORS + 1))
fi
if [[ -x "$SKILL_DIR/scripts/delegate.sh" ]] || $DRY_RUN; then
info "scripts/delegate.sh: OK"
else
error "scripts/delegate.sh: not found or not executable"
ERRORS=$((ERRORS + 1))
fi
if [[ -x "$SKILL_DIR/scripts/tmux-session.sh" ]] || $DRY_RUN; then
info "scripts/tmux-session.sh: OK"
else
error "scripts/tmux-session.sh: not found or not executable"
ERRORS=$((ERRORS + 1))
fi
# --- Auth check ---
echo ""
echo "Checking auth..."
echo " Claude Code: run 'claude auth' to verify subscription/OAuth is configured"
echo " Codex: run 'codex auth' to verify OpenAI subscription is configured"
echo ""
echo " NOTE: The delegation scripts strip API keys from the sub-process"
echo " environment. Both CLIs must be configured with subscription/OAuth"
echo " auth. API key auth will NOT work."
# --- Summary ---
echo ""
if [[ $ERRORS -eq 0 ]]; then
echo "============================================"
info "Installation complete."
echo ""
echo " Skill installed to: $SKILL_DIR"
echo ""
echo " Delegate a task:"
echo " $SKILL_DIR/scripts/delegate.sh --prompt 'Your task' --workdir ~/project"
echo ""
echo " Long-running task in tmux:"
echo " $SKILL_DIR/scripts/tmux-session.sh --name my-task --prompt 'Your task' --workdir ~/project"
echo ""
echo " OpenClaw will discover the skill automatically on next session."
echo "============================================"
else
error "$ERRORS verification error(s). Check the output above."
exit 1
fi

View File

@ -0,0 +1,76 @@
# Example Delegation Policy
Adopt or adapt this policy to govern how your agent delegates tasks to Claude Code and Codex sub-processes.
## When to Delegate
Delegate when the task involves:
- Writing or modifying code (Python, TypeScript, shell scripts, etc.)
- Generating files (documents, spreadsheets, presentations)
- Complex multi-step coding workflows (build, test, commit)
- Refactoring or code review
Do NOT delegate:
- Trivial one-liners (a single sed/awk/jq command)
- JSON/YAML config edits
- Tasks that only need MCP tools (email, calendar, search)
## Pre-Delegation Checklist
Before spawning a sub-process, complete these steps:
1. **Identify context** — what project, codebase, or domain does this relate to?
2. **Gather facts** — collect all data the sub-process will need (it has no conversation history or MCP access, only filesystem access in the working directory)
3. **Compose a self-contained prompt** — include context, facts, requirements, and output format
4. **Apply anti-hallucination rules** — list verified facts and instruct the sub-process to use only those
## Prompt Template
```markdown
# Task: [description]
## Context
[Background the sub-process needs to understand the task]
## Verified Facts (use ONLY these)
- [Fact 1]
- [Fact 2]
- [Fact N]
## Anti-Hallucination Rule
Do not invent facts, numbers, names, or data not listed above.
If information is missing, use [TBD] as placeholder.
## Requirements
[Deliverable specifications — structure, format, audience, constraints]
## Output
[File format, naming convention, save location]
```
## Agent Selection Policy
- **Default to Claude Code** for all coding and generation tasks
- **Use Codex only when the user explicitly requests it**
- Never auto-select Codex based on task type
## Security Policy
- Always strip AI provider API keys from the sub-process environment
- The delegation scripts strip 23 known provider keys (see `SKILL.md` for the full list). The list is not exhaustive — add any additional keys your environment uses
- This forces subscription/OAuth auth and prevents key leakage
- For manual invocations, use `env -u ANTHROPIC_API_KEY -u OPENAI_API_KEY ...` at minimum
## Result Handling
1. Always poll for completion — never end your turn after launching a background task
2. Verify the output file exists
3. Summarize results (do not dump raw logs)
4. If the sub-process failed, read the log and either retry with fixes or report the failure
## Session Resumption
When the user requests changes to a previous delegation:
- Resume the existing session with `--continue` or `--resume <session-id>`
- No need to re-compose the full context
- State only the amendments needed

View File

@ -0,0 +1,213 @@
#!/usr/bin/env bash
# delegate.sh — Launch Claude Code or Codex as a sub-process.
#
# Runs a delegation with API key stripping for security.
# Supports prompt strings, prompt files, foreground and background
# execution.
#
# Usage:
# delegate.sh --prompt "Your task" [options]
# delegate.sh --file /path/to/prompt.md [options]
#
# Options:
# --prompt TEXT Inline prompt string
# --file PATH Read prompt from file
# --agent AGENT "claude" (default) or "codex"
# --workdir DIR Working directory for the sub-process
# --log PATH Log file path (default: /tmp/delegation-<timestamp>.log)
# --background Run in background, return immediately
# --full-auto Codex: enable full-auto mode (default for codex)
# --timeout SECS Kill sub-process after N seconds (default: 3600)
# -h, --help Show this help message
set -euo pipefail
# --- Defaults ---
AGENT="claude"
PROMPT=""
PROMPT_FILE=""
WORKDIR="${PWD}"
LOG_FILE=""
BACKGROUND=false
FULL_AUTO=true
TIMEOUT=3600
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--prompt|--file|--agent|--workdir|--log|--timeout)
if [[ $# -lt 2 ]]; then
echo "Error: $1 requires a value" >&2
exit 1
fi
;;&
--prompt) PROMPT="$2"; shift 2 ;;
--file) PROMPT_FILE="$2"; shift 2 ;;
--agent) AGENT="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--log) LOG_FILE="$2"; shift 2 ;;
--background) BACKGROUND=true; shift ;;
--full-auto) FULL_AUTO=true; shift ;;
--no-full-auto) FULL_AUTO=false; shift ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
-h|--help)
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# --- Validate ---
if [[ -z "$PROMPT" && -z "$PROMPT_FILE" ]]; then
echo "Error: --prompt or --file is required" >&2
exit 1
fi
if [[ -n "$PROMPT_FILE" ]]; then
if [[ ! -f "$PROMPT_FILE" ]]; then
echo "Error: prompt file not found: $PROMPT_FILE" >&2
exit 1
fi
PROMPT="$(cat "$PROMPT_FILE")"
fi
if [[ -z "$PROMPT" ]]; then
echo "Error: prompt is empty" >&2
exit 1
fi
if [[ "$AGENT" != "claude" && "$AGENT" != "codex" ]]; then
echo "Error: --agent must be 'claude' or 'codex'" >&2
exit 1
fi
# Check agent binary exists
if ! command -v "$AGENT" &> /dev/null; then
if [[ "$AGENT" == "claude" ]]; then
echo "Error: 'claude' is not installed. Install with: npm install -g @anthropic-ai/claude-code" >&2
else
echo "Error: 'codex' is not installed. Install with: npm install -g @openai/codex" >&2
fi
exit 1
fi
# Check required utilities
if ! command -v timeout &> /dev/null; then
echo "Error: 'timeout' is required but not found" >&2
exit 1
fi
if [[ "$AGENT" == "codex" ]]; then
if ! command -v script &> /dev/null; then
echo "Error: 'script' (from util-linux) is required for Codex PTY support" >&2
exit 1
fi
# BSD script (macOS) does not support -c flag
if [[ "$(uname)" == "Darwin" ]]; then
echo "Error: Codex delegation via delegate.sh requires Linux (GNU script)." >&2
echo "On macOS, use tmux-session.sh or run Codex directly with a PTY." >&2
exit 1
fi
fi
if [[ ! -d "$WORKDIR" ]]; then
echo "Error: working directory not found: $WORKDIR" >&2
exit 1
fi
# Codex requires a git repository — fail fast instead of silently mutating
if [[ "$AGENT" == "codex" ]]; then
if ! (cd "$WORKDIR" && { [[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1; }); then
echo "Error: Codex requires a git repository but $WORKDIR is not one." >&2
echo "Initialize one with: cd $WORKDIR && git init" >&2
exit 1
fi
fi
# --- Set up log file ---
if [[ -z "$LOG_FILE" ]]; then
LOG_FILE="/tmp/delegation-$(date +%s)-$$.log"
fi
mkdir -p "$(dirname "$LOG_FILE")"
# --- Strip AI provider credentials from environment ---
# Prevents key leakage to child processes. Forces subscription/OAuth auth.
for var in ANTHROPIC_API_KEY OPENAI_API_KEY GOOGLE_API_KEY GOOGLE_GENERATIVE_AI_API_KEY \
AZURE_OPENAI_API_KEY COHERE_API_KEY MISTRAL_API_KEY OPENROUTER_API_KEY \
DEEPSEEK_API_KEY TOGETHER_API_KEY FIREWORKS_API_KEY GROQ_API_KEY \
GEMINI_API_KEY PERPLEXITY_API_KEY BRAVE_API_KEY BRAVE_SEARCH_API_KEY \
REPLICATE_API_TOKEN AI21_API_KEY HUGGINGFACE_API_KEY HF_TOKEN \
VOYAGE_API_KEY ANYSCALE_API_KEY XAI_API_KEY; do
unset "$var" 2>/dev/null || true
done
# --- Build command ---
build_claude_cmd() {
echo "claude --permission-mode bypassPermissions --print"
}
build_codex_cmd() {
local cmd="codex exec"
if $FULL_AUTO; then
cmd="codex exec --full-auto"
fi
echo "$cmd"
}
run_delegation() {
local exit_code=0
cd "$WORKDIR" || exit 1
case "$AGENT" in
claude)
$(build_claude_cmd) "$PROMPT" > "$LOG_FILE" 2>&1 || exit_code=$?
;;
codex)
# Codex requires a PTY — use script(1) to provide one.
# -e: propagate child exit code (without it, script always returns 0)
# Run through bash -c so printf '%q' quoting is interpreted correctly
# (script passes commands to /bin/sh which may be dash, not bash).
script -e -q -c "bash -c $(printf '%q' "$(build_codex_cmd) $(printf '%q' "$PROMPT")")" "$LOG_FILE" || exit_code=$?
;;
esac
return $exit_code
}
# --- Execute ---
echo "Delegating to $AGENT in $WORKDIR (timeout: ${TIMEOUT}s)"
echo "Log: $LOG_FILE"
ESCAPED_WORKDIR="$(printf '%q' "$WORKDIR")"
ESCAPED_LOG="$(printf '%q' "$LOG_FILE")"
if $BACKGROUND; then
(
EXIT_CODE=0
timeout "$TIMEOUT" bash -c "$(declare -f run_delegation build_claude_cmd build_codex_cmd); \
AGENT='$AGENT' PROMPT='$(printf '%s' "$PROMPT" | sed "s/'/'\\\\''/g")' \
WORKDIR=$ESCAPED_WORKDIR LOG_FILE=$ESCAPED_LOG FULL_AUTO=$FULL_AUTO \
run_delegation" || EXIT_CODE=$?
echo ""
echo "--- Delegation complete (exit code: $EXIT_CODE) ---"
) >> "$LOG_FILE" 2>&1 &
BG_PID=$!
echo "Background PID: $BG_PID"
echo "Monitor: tail -f $LOG_FILE"
else
EXIT_CODE=0
timeout "$TIMEOUT" bash -c "$(declare -f run_delegation build_claude_cmd build_codex_cmd); \
AGENT='$AGENT' PROMPT='$(printf '%s' "$PROMPT" | sed "s/'/'\\\\''/g")' \
WORKDIR=$ESCAPED_WORKDIR LOG_FILE=$ESCAPED_LOG FULL_AUTO=$FULL_AUTO \
run_delegation" || EXIT_CODE=$?
if [[ $EXIT_CODE -eq 0 ]]; then
echo "Delegation complete."
elif [[ $EXIT_CODE -eq 124 ]]; then
echo "Delegation timed out after ${TIMEOUT}s. Check log: $LOG_FILE" >&2
else
echo "Delegation failed (exit code: $EXIT_CODE). Check log: $LOG_FILE" >&2
fi
exit $EXIT_CODE
fi

View File

@ -0,0 +1,199 @@
#!/usr/bin/env bash
# tmux-session.sh — Manage long-running Claude Code / Codex delegations in tmux.
#
# Usage:
# tmux-session.sh --name SESSION --prompt "task" [options]
# tmux-session.sh --attach SESSION
# tmux-session.sh --status SESSION
# tmux-session.sh --list
# tmux-session.sh --kill SESSION
#
# Options:
# --name NAME Session name (required for new sessions)
# --prompt TEXT Task prompt
# --file PATH Read prompt from file (alternative to --prompt)
# --agent AGENT "claude" (default) or "codex"
# --workdir DIR Working directory (default: current directory)
# --attach NAME Attach to an existing session
# --status NAME Check if a session is active and show last output
# --list List all delegation sessions
# --kill NAME Kill a session
# -h, --help Show this help message
set -euo pipefail
SESSION_PREFIX="delegation"
ACTION=""
SESSION_NAME=""
AGENT="claude"
PROMPT=""
PROMPT_FILE=""
WORKDIR="${PWD}"
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--name|--prompt|--file|--agent|--workdir|--attach|--status|--kill)
if [[ $# -lt 2 ]]; then
echo "Error: $1 requires a value" >&2
exit 1
fi
;;&
--name) SESSION_NAME="$2"; ACTION="${ACTION:-create}"; shift 2 ;;
--prompt) PROMPT="$2"; shift 2 ;;
--file) PROMPT_FILE="$2"; shift 2 ;;
--agent) AGENT="$2"; shift 2 ;;
--workdir) WORKDIR="$2"; shift 2 ;;
--attach) ACTION="attach"; SESSION_NAME="$2"; shift 2 ;;
--status) ACTION="status"; SESSION_NAME="$2"; shift 2 ;;
--list) ACTION="list"; shift ;;
--kill) ACTION="kill"; SESSION_NAME="$2"; shift 2 ;;
-h|--help)
sed -n '2,/^$/p' "$0" | sed 's/^# \?//'
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# --- Check tmux ---
if ! command -v tmux &> /dev/null; then
echo "Error: tmux is not installed" >&2
exit 1
fi
full_session_name() {
echo "${SESSION_PREFIX}-${1}"
}
# --- Actions ---
case "$ACTION" in
list)
echo "Active delegation sessions:"
tmux list-sessions -F '#{session_name} (created #{session_created_string})' 2>/dev/null \
| grep "^${SESSION_PREFIX}-" \
| sed "s/^${SESSION_PREFIX}-/ /" \
|| echo " (none)"
;;
status)
FULL_NAME="$(full_session_name "$SESSION_NAME")"
if tmux has-session -t "$FULL_NAME" 2>/dev/null; then
echo "Session '$SESSION_NAME' is active."
echo ""
echo "--- Last 20 lines of output ---"
tmux capture-pane -t "$FULL_NAME" -p | tail -20
else
echo "Session '$SESSION_NAME' is not running."
fi
;;
attach)
FULL_NAME="$(full_session_name "$SESSION_NAME")"
if tmux has-session -t "$FULL_NAME" 2>/dev/null; then
tmux attach -t "$FULL_NAME"
else
echo "Session '$SESSION_NAME' not found." >&2
exit 1
fi
;;
kill)
FULL_NAME="$(full_session_name "$SESSION_NAME")"
if tmux has-session -t "$FULL_NAME" 2>/dev/null; then
tmux kill-session -t "$FULL_NAME"
echo "Session '$SESSION_NAME' killed."
else
echo "Session '$SESSION_NAME' not found." >&2
exit 1
fi
;;
create)
if [[ -z "$SESSION_NAME" ]]; then
echo "Error: --name is required" >&2
exit 1
fi
# Check agent binary exists
if ! command -v "$AGENT" &> /dev/null; then
echo "Error: '$AGENT' is not installed. Install with: npm install -g @anthropic-ai/claude-code (or @openai/codex)" >&2
exit 1
fi
if [[ ! -d "$WORKDIR" ]]; then
echo "Error: working directory not found: $WORKDIR" >&2
exit 1
fi
# Codex requires a git repo
if [[ "$AGENT" == "codex" ]]; then
if ! (cd "$WORKDIR" && { [[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1; }); then
echo "Error: Codex requires a git repository but $WORKDIR is not one." >&2
echo "Initialize one with: cd $WORKDIR && git init" >&2
exit 1
fi
fi
if [[ -z "$PROMPT" && -z "$PROMPT_FILE" ]]; then
echo "Error: --prompt or --file is required" >&2
exit 1
fi
if [[ -n "$PROMPT_FILE" ]]; then
if [[ ! -f "$PROMPT_FILE" ]]; then
echo "Error: prompt file not found: $PROMPT_FILE" >&2
exit 1
fi
PROMPT="$(cat "$PROMPT_FILE")"
fi
if [[ -z "$PROMPT" ]]; then
echo "Error: prompt is empty" >&2
exit 1
fi
FULL_NAME="$(full_session_name "$SESSION_NAME")"
if tmux has-session -t "$FULL_NAME" 2>/dev/null; then
echo "Session '$SESSION_NAME' already exists. Use --attach or --kill first." >&2
exit 1
fi
# Strip AI provider credentials from environment
STRIP_VARS="ANTHROPIC_API_KEY OPENAI_API_KEY GOOGLE_API_KEY GOOGLE_GENERATIVE_AI_API_KEY"
STRIP_VARS="$STRIP_VARS AZURE_OPENAI_API_KEY COHERE_API_KEY MISTRAL_API_KEY OPENROUTER_API_KEY"
STRIP_VARS="$STRIP_VARS DEEPSEEK_API_KEY TOGETHER_API_KEY FIREWORKS_API_KEY GROQ_API_KEY"
STRIP_VARS="$STRIP_VARS GEMINI_API_KEY PERPLEXITY_API_KEY BRAVE_API_KEY BRAVE_SEARCH_API_KEY"
STRIP_VARS="$STRIP_VARS REPLICATE_API_TOKEN AI21_API_KEY HUGGINGFACE_API_KEY HF_TOKEN"
STRIP_VARS="$STRIP_VARS VOYAGE_API_KEY ANYSCALE_API_KEY XAI_API_KEY"
ENV_STRIP=""
for v in $STRIP_VARS; do ENV_STRIP="$ENV_STRIP -u $v"; done
# Build the command
case "$AGENT" in
claude)
CMD="cd $(printf '%q' "$WORKDIR") && env $ENV_STRIP claude --permission-mode bypassPermissions --print $(printf '%q' "$PROMPT")"
;;
codex)
CMD="cd $(printf '%q' "$WORKDIR") && env $ENV_STRIP codex exec --full-auto $(printf '%q' "$PROMPT")"
;;
*)
echo "Error: --agent must be 'claude' or 'codex'" >&2
exit 1
;;
esac
tmux new-session -d -s "$FULL_NAME" -c "$WORKDIR" "$CMD; echo ''; echo '--- Session complete. Press Enter to close. ---'; read"
echo "Session '$SESSION_NAME' started."
echo " Attach: $0 --attach $SESSION_NAME"
echo " Status: $0 --status $SESSION_NAME"
echo " Kill: $0 --kill $SESSION_NAME"
;;
*)
echo "Error: specify an action (--name, --attach, --status, --list, --kill)" >&2
echo "Run with --help for usage." >&2
exit 1
;;
esac