#!/bin/bash # Runs in each session's "claude" window. Resumes the conversation recorded for # this session (see devdesk-session-hook), starts a fresh one if there is none, # and offers a small menu whenever Claude exits so the window never just dies. set -uo pipefail # shellcheck source=libexec/devdesk-lib.sh . "${DEVDESK_LIBEXEC:-/usr/lib/devdesk}/devdesk-lib.sh" NAME=$(dd_current_session) [ -n "$NAME" ] || dd_die "not running inside a DevDesk session" SETTINGS="$DEVDESK_SHARE/claude-settings.json" # Re-read /etc/devdesk/env before every Claude launch, so changes (Remote # Control, CLAUDE_ARGS, …) apply on the next restart of Claude in a session. reload_config() { dd_load_env CLAUDE_BIN=${CLAUDE_BIN:-claude} read -r -a EXTRA <<<"${CLAUDE_ARGS:-}" } ORANGE=$'\033[38;2;255;107;0m' CYAN=$'\033[38;2;0;212;255m' MUTED=$'\033[38;2;62;96;122m' RESET=$'\033[0m' common_args() { printf '%s\0' --settings "$SETTINGS" # Per session since devdesk#61 (Settings → Remote Control). if [ "$(dd_rc_wanted "$NAME")" = 1 ]; then printf '%s\0' --remote-control "devdesk-$NAME" fi if [ ${#EXTRA[@]} -gt 0 ]; then printf '%s\0' "${EXTRA[@]}" fi } run_new() { local args prompt=() file="$DEVDESK_STATE_DIR/prompts/$NAME.txt" mapfile -d '' args < <(common_args) # A first prompt left by `devdesk attach --investigate` (Ops Alerts → Ask # Claude, devdesk#39): sent once, right after the session name so options # in CLAUDE_ARGS can't swallow it. if [ -s "$file" ]; then prompt=("$(cat "$file")") rm -f "$file" fi "$CLAUDE_BIN" -n "$NAME" "${prompt[@]}" "${args[@]}" } run_resume() { local id args fork=() id=$(dd_registry_get "$NAME" claude_session_id) if [ -z "$id" ]; then run_new return fi # Start fresh only when the conversation's file is really gone. Any other # failure (an early Ctrl+C, a network error) leaves the recorded id alone # and ends at the menu, where ⏎ tries again (devdesk#12). if [ -z "$(dd_convo_file "$id")" ]; then printf "%sConversation %s is no longer in Claude's history — starting a new one.%s\n" "$MUTED" "$id" "$RESET" sleep 1 run_new return fi mapfile -d '' args < <(common_args) # "Fork" from conversation search (devdesk#24): continue as a new # conversation, once; the SessionStart hook then records the new id. if [ "$(dd_registry_get "$NAME" fork)" = true ]; then fork=(--fork-session) # shellcheck disable=SC2016 # $n is a jq variable dd_registry_update 'del(.sessions[$n].fork)' --arg n "$NAME" 2>/dev/null || true fi "$CLAUDE_BIN" --resume "$id" "${fork[@]}" "${args[@]}" } menu() { printf '\n%s◈ Claude exited%s %s(session %s, code %s)%s\n' "$ORANGE" "$RESET" "$MUTED" "$NAME" "$1" "$RESET" printf ' %s[⏎]%s resume %s[n]%s new conversation %s[s]%s shell %s[x]%s end session\n' \ "$CYAN" "$RESET" "$CYAN" "$RESET" "$CYAN" "$RESET" "$CYAN" "$RESET" } printf '\033]2;%s\033\134' "$NAME" # pane title (ST = ESC \\) # While a DevDesk tab shows this session, this marker exists and Claude tells # Remote Control a client is present, which holds back phone pushes (devdesk#26). # Keyed by tmux's session id, which survives renames. sid=$(dd_tmux display -p -t "${TMUX_PANE:-}" '#{session_id}' 2>/dev/null) if [ -n "$sid" ]; then export CLAUDE_CLIENT_PRESENCE_FILE="$DEVDESK_PRESENCE_DIR/${sid#\$}" fi mode=resume while :; do NAME=$(dd_current_session) # follow renames reload_config if [ "$mode" = new ]; then run_new; else run_resume; fi rc=$? NAME=$(dd_current_session) # Settings changed Remote Control: Claude was stopped to restart on the same # conversation with the new setting (devdesk#61), no menu. restart="$DEVDESK_STATE_DIR/restart/$NAME" if [ -e "$restart" ]; then rm -f "$restart" printf '\n%s◈ Restarting Claude with Remote Control %s…%s\n' "$MUTED" "$([ "$(dd_rc_wanted "$NAME")" = 1 ] && echo on || echo off)" "$RESET" mode=resume continue fi while :; do menu "$rc" IFS= read -rsn1 key || exit 0 case $key in "") mode=resume; break ;; n | N) mode=new; break ;; s | S) printf '%sShell in %s — exit to return to this menu.%s\n' "$MUTED" "$PWD" "$RESET" "${SHELL:-/bin/bash}" -l ;; x | X) printf 'End session %s? This stops Claude and forgets the session. [y/N] ' "$NAME" IFS= read -rsn1 confirm || exit 0 echo if [ "$confirm" = y ] || [ "$confirm" = Y ]; then dd_registry_remove "$NAME" tmux kill-session -t "=$NAME" 2>/dev/null exit 0 fi ;; esac done done