add launch-assistant.sh: cc-ci-assistant — remote-control, non-loop helper

A general-purpose Claude session sharing the orchestrator's workspace + access,
under remote-control (cc-ci-assistant), NOT on a loop. Sits idle until the
orchestrator/operator hands it a plan/task, does it, reports, waits. Modelled on
launch-orchestrator.sh: persistent pinned session-id (resume across relaunch),
root-aware --dangerously-skip-permissions handling, start/fresh/status/attach/stop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-05-30 23:52:08 +00:00
parent b550d6c432
commit 2233c6182a

102
cc-ci-plan/launch-assistant.sh Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env bash
#
# launch-assistant.sh — start/resume the cc-ci ASSISTANT session in tmux under remote-control.
#
# The Assistant is a general-purpose, remote-controllable Claude session that shares the cc-ci
# workspace + access with the orchestrator, but is NOT on a loop. It sits idle until the orchestrator
# (or the operator) hands it a plan/task; it does the task against the workspace, reports back, and
# waits for the next one. Modelled on launch-orchestrator.sh.
#
# Naming: tmux session AND remote-control name are both "cc-ci-assistant" (matching
# cc-ci-orch / cc-ci-builder / cc-ci-adv / cc-ci-watchdog).
#
# Usage:
# ./launch-assistant.sh start # resume the persistent assistant session (DEFAULT); creates it on first run
# ./launch-assistant.sh fresh # start a NEW assistant session (new id)
# ./launch-assistant.sh status # show tmux + remote-control state
# ./launch-assistant.sh attach # tmux attach (Ctrl-b d to detach)
# ./launch-assistant.sh stop # kill the tmux session (conversation persists on disk)
set -euo pipefail
SESSION="${ASSISTANT_SESSION:-cc-ci-assistant}" # tmux session name == remote-control name
WORKDIR="${ASSISTANT_DIR:-/srv/cc-ci}" # same workspace as the orchestrator
CLAUDE_BIN="${CLAUDE_BIN:-/home/loops/.local/bin/claude}"
# --dangerously-skip-permissions is blocked for root (use the env var there); as a non-root user the
# flag works. Mirror launch.sh's detection.
if [ "$(id -u)" = "0" ]; then export CLAUDE_DANGEROUSLY_SKIP_PERMISSIONS=1; CLAUDE_FLAGS="${CLAUDE_FLAGS:-}";
else CLAUDE_FLAGS="${CLAUDE_FLAGS:---dangerously-skip-permissions}"; fi
REMOTE_CONTROL="${REMOTE_CONTROL:-1}" # 1 => --remote-control (viewable at claude.ai/code)
LOG_DIR="${LOG_DIR:-/srv/cc-ci/.cc-ci-logs}"
ID_FILE="${ASSISTANT_ID_FILE:-$LOG_DIR/.assistant-session-id}"
# Startup brief: defines the assistant's role. Injected as the session's first/next turn. No single quotes.
STARTUP_PROMPT="${ASSISTANT_STARTUP_PROMPT-You are the cc-ci ASSISTANT — a general-purpose helper sharing the cc-ci workspace (/srv/cc-ci) and access (ssh cc-ci, .testenv, the plan files) with the orchestrator. You are NOT on a loop and you do NOT supervise the Builder/Adversary loops. Sit idle until the orchestrator or operator hands you a specific plan or task; then do it carefully, report the result, and wait for the next one. Respect single-writer discipline: the loops own the cc-ci product-repo clones (/srv/cc-ci/cc-ci, /srv/cc-ci/cc-ci-adv) — do not edit those unless a task explicitly says to. If you have just (re)launched and have no pending task, briefly confirm you are online and idle, then wait.}"
log() { printf '[assistant %(%H:%M:%S)T] %s\n' -1 "$*"; }
die() { log "ERROR: $*"; exit 1; }
session_alive() { tmux has-session -t "$SESSION" 2>/dev/null; }
preflight() {
command -v tmux >/dev/null 2>&1 || die "missing dependency: tmux"
command -v "$CLAUDE_BIN" >/dev/null 2>&1 || die "claude CLI not found at $CLAUDE_BIN (set CLAUDE_BIN)"
[[ -d "$WORKDIR" ]] || die "workdir not found: $WORKDIR"
mkdir -p "$LOG_DIR"
# seed a stable session id on first ever launch
[[ -f "$ID_FILE" ]] || cat /proc/sys/kernel/random/uuid > "$ID_FILE"
}
sid() { cat "$ID_FILE" 2>/dev/null; }
# does a transcript already exist for this id? (project dir derived from WORKDIR, e.g. -srv-cc-ci)
have_transcript() {
local key; key="$(printf '%s' "$WORKDIR" | sed 's#/#-#g')"
[[ -f "$HOME/.claude/projects/$key/$(sid).jsonl" ]]
}
# $1 = resume|fresh
start() {
local mode="${1:-resume}"
preflight
if session_alive; then
log "$SESSION already running — leaving it (use '$0 stop' first to relaunch)"; return 0
fi
local rc="" sess="" id; id="$(sid)"
[[ "$REMOTE_CONTROL" == "1" ]] && rc="--remote-control '$SESSION'"
if [[ "$mode" == "fresh" ]]; then
id="$(cat /proc/sys/kernel/random/uuid)"; echo "$id" > "$ID_FILE"
sess="--session-id '$id'"; log "starting $SESSION FRESH (new id=$id)"
elif have_transcript; then
sess="--resume '$id'"; log "starting $SESSION (resume id=$id)"
else
sess="--session-id '$id'"; log "starting $SESSION (first run, pin id=$id)"
fi
local prompt_arg=""
[[ -n "$STARTUP_PROMPT" ]] && prompt_arg="'$STARTUP_PROMPT'"
tmux new-session -d -s "$SESSION" -c "$WORKDIR" \
"$CLAUDE_BIN $sess $rc $CLAUDE_FLAGS $prompt_arg"
tmux pipe-pane -o -t "$SESSION" "cat >> '$LOG_DIR/$SESSION.log'"
log "started. status: $0 status | attach: tmux attach -t $SESSION | id: $id"
}
case "${1:-start}" in
start) start resume ;;
fresh) start fresh ;;
stop) if session_alive; then log "killing $SESSION"; tmux kill-session -t "$SESSION" || true; else log "$SESSION not running"; fi ;;
status)
if session_alive; then log "$SESSION: RUNNING"; ps -eo pid,etime,args | grep "[r]emote-control $SESSION" || true
else log "$SESSION: stopped"; fi
log "session id: $(sid) (file: $ID_FILE)" ;;
attach) exec tmux attach -t "$SESSION" ;;
*)
cat <<EOF
cc-ci assistant launcher — a remote-controllable, NON-loop helper sharing the orchestrator's workspace.
$0 start resume (or first-run create) the assistant session in tmux + remote-control (default)
$0 fresh start a NEW assistant session (new id)
$0 status show tmux + remote-control state and the session id
$0 attach tmux attach to the session
$0 stop kill the tmux session (conversation persists on disk)
Env: SESSION=$SESSION WORKDIR=$WORKDIR REMOTE_CONTROL=$REMOTE_CONTROL CLAUDE_BIN=$CLAUDE_BIN
The orchestrator hands it plans/tasks; it does them, reports, and waits. Not on a loop.
EOF
;;
esac