feat(@scripts/session-tools): ✨ update priority convention to p0-p4 triage system
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
4daae305ab
commit
a5cb5d74c1
2 changed files with 166 additions and 12 deletions
|
|
@ -142,7 +142,7 @@ SYSTEM_PROMPT = """You triage Claude Code coding sessions. For each session in t
|
|||
- ref_index: integer matching the input's ref_index
|
||||
- summary: ONE short sentence describing what's happening
|
||||
- status: one of done, in_progress, blocked, waiting_on_user, abandoned
|
||||
- priority: integer 1-5 (5 = critical to resume now, 1 = abandonable)
|
||||
- priority: integer 0-4 (0 = critical to resume now, 4 = abandonable). Follows the P0/P1 incident convention — LOWER number means HIGHER priority.
|
||||
- next_action: ONE short imperative phrase, or empty string if status is done/abandoned
|
||||
|
||||
Output ONLY a JSON array. No markdown, no prose."""
|
||||
|
|
@ -217,7 +217,8 @@ async def main_async(args: argparse.Namespace) -> None:
|
|||
# the cache BEFORE reading the JSONL (the expensive part for a triage run
|
||||
# over hundreds of sessions where most are unchanged).
|
||||
cache = ResponseCache(CACHE_DIR)
|
||||
template_id = "rclaude-triage-v1"
|
||||
# v2: priority scale flipped to P0=critical / P4=abandonable convention.
|
||||
template_id = "rclaude-triage-v2"
|
||||
cached_results: list[dict] = []
|
||||
uncached: list[tuple[int, Path, str]] = []
|
||||
for mtime, jsonl in candidates:
|
||||
|
|
@ -288,14 +289,17 @@ async def main_async(args: argparse.Namespace) -> None:
|
|||
await client.close()
|
||||
results.extend(new_results)
|
||||
|
||||
# Lower priority number = higher importance (P0 convention). Sort
|
||||
# ascending by priority, descending by mtime so the most-recent within
|
||||
# each priority bucket floats up.
|
||||
def sort_key(r: dict) -> tuple[int, int]:
|
||||
try:
|
||||
prio = int(r.get("priority", 0))
|
||||
prio = int(r.get("priority", 9))
|
||||
except (TypeError, ValueError):
|
||||
prio = 0
|
||||
return (prio, int(r.get("mtime", 0)))
|
||||
prio = 9
|
||||
return (prio, -int(r.get("mtime", 0)))
|
||||
|
||||
for r in sorted(results, key=sort_key, reverse=True):
|
||||
for r in sorted(results, key=sort_key):
|
||||
line = "\t".join(
|
||||
[
|
||||
str(r.get("mtime", 0)),
|
||||
|
|
|
|||
154
bin/rclaude
154
bin/rclaude
|
|
@ -326,6 +326,121 @@ _REMOTE_TRIAGE_BOOT='export PATH=$HOME/.local/bin:/opt/homebrew/bin:$PATH; '"$_P
|
|||
# work itself is already protected by tmux on the remote.
|
||||
_SSH_LIVE_OPTS='-o ServerAliveInterval=30 -o ServerAliveCountMax=6 -o TCPKeepAlive=yes'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Setup / dependency auto-install
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Cache dir for per-host setup markers. A marker file means we've already
|
||||
# probed + installed deps on that host within RCLAUDE_SETUP_TTL days.
|
||||
_SETUP_CACHE_DIR=${XDG_CACHE_HOME:-$HOME/.cache}/rclaude
|
||||
_SETUP_TTL_DAYS=${RCLAUDE_SETUP_TTL:-7}
|
||||
|
||||
# Detect package-manager family on <host>. Output: macos | rhel | debian | unknown.
|
||||
detect_os_on() {
|
||||
_h=$1
|
||||
_probe='if [ "$(uname -s)" = "Darwin" ]; then echo macos
|
||||
elif command -v dnf >/dev/null 2>&1; then echo rhel
|
||||
elif command -v apt-get >/dev/null 2>&1; then echo debian
|
||||
else echo unknown; fi'
|
||||
if is_local "$_h"; then
|
||||
sh -c "$_probe" 2>/dev/null
|
||||
else
|
||||
ssh -o BatchMode=yes -o ConnectTimeout=5 "$_h" "$_probe" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Install <pkgs> on <host> using its native package manager. Uses sudo for
|
||||
# system pkgs on Linux; brew (no sudo) on macOS.
|
||||
install_pkgs_on() {
|
||||
_h=$1; _os=$2; shift 2
|
||||
_pkgs=$*
|
||||
[ -z "$_pkgs" ] && return 0
|
||||
case $_os in
|
||||
macos) _cmd="brew install $_pkgs" ;;
|
||||
rhel) _cmd="sudo dnf install -y $_pkgs" ;;
|
||||
debian) _cmd="sudo apt-get update && sudo apt-get install -y $_pkgs" ;;
|
||||
*)
|
||||
echo "rclaude: don't know how to install $_pkgs on $_h ($_os) — do it manually" >&2
|
||||
return 1 ;;
|
||||
esac
|
||||
printf 'rclaude: installing on %s: %s\n' "$_h" "$_pkgs" >&2
|
||||
if is_local "$_h"; then
|
||||
sh -c "$_cmd" >&2
|
||||
else
|
||||
ssh -t "$_h" "$_cmd" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Run a probe command on <host>, return its stdout. Used by setup_host.
|
||||
_probe_on() {
|
||||
_h=$1; _cmd=$2
|
||||
if is_local "$_h"; then sh -c "$_cmd" 2>/dev/null
|
||||
else ssh -o BatchMode=yes -o ConnectTimeout=5 "$_h" "$_cmd" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Idempotently install rclaude's deps on <host>. Honors a per-host marker so
|
||||
# we don't re-probe on every invocation. Pass `force` to bypass the marker.
|
||||
setup_host() {
|
||||
_h=$1; _force=${2:-}
|
||||
mkdir -p "$_SETUP_CACHE_DIR" 2>/dev/null
|
||||
_marker_id=$(printf %s "$_h" | tr -c 'A-Za-z0-9' '_')
|
||||
_marker="$_SETUP_CACHE_DIR/setup-$_marker_id"
|
||||
if [ "$_force" != "force" ] && [ -f "$_marker" ]; then
|
||||
# Marker exists and is recent enough → assume deps are fine.
|
||||
if [ -z "$(find "$_marker" -mtime +"$_SETUP_TTL_DAYS" 2>/dev/null)" ]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
_os=$(detect_os_on "$_h")
|
||||
if [ "$_os" = "unknown" ] || [ -z "$_os" ]; then
|
||||
echo "rclaude: couldn't detect OS on $_h; skipping setup" >&2
|
||||
return 0
|
||||
fi
|
||||
# Probe system binaries.
|
||||
_missing=""
|
||||
_have=$(_probe_on "$_h" 'for c in tmux rsync mosh; do command -v "$c" >/dev/null 2>&1 && echo "$c"; done')
|
||||
for c in tmux rsync mosh; do
|
||||
case " $_have " in *" $c "*) ;; *) _missing="$_missing $c" ;; esac
|
||||
done
|
||||
if [ -n "$_missing" ]; then
|
||||
install_pkgs_on "$_h" "$_os" $_missing || true
|
||||
fi
|
||||
# Python SDK for triage. Try to install per-user without sudo.
|
||||
_has_sdk=$(_probe_on "$_h" 'for p in python3.13 python3.12 python3.11 python3; do b=$(command -v "$p" 2>/dev/null) || continue; "$b" -c "import claude_code_batch_sdk" 2>/dev/null && echo "$b" && break; done')
|
||||
if [ -z "$_has_sdk" ]; then
|
||||
_pick=$(_probe_on "$_h" 'for p in python3.12 python3.11 python3; do command -v "$p" 2>/dev/null && break; done | head -1')
|
||||
if [ -n "$_pick" ]; then
|
||||
printf 'rclaude: installing claude-code-batch-sdk via %s on %s\n' "$_pick" "$_h" >&2
|
||||
if is_local "$_h"; then
|
||||
"$_pick" -m pip install --user --quiet claude-code-batch-sdk >&2 || true
|
||||
else
|
||||
ssh "$_h" "$_pick -m pip install --user --quiet claude-code-batch-sdk" >&2 || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
touch "$_marker" 2>/dev/null
|
||||
}
|
||||
|
||||
# Prefer mosh when available on both ends — unless explicitly disabled via
|
||||
# RCLAUDE_TRANSPORT=ssh. Echoes "mosh" or "ssh". Caches result per host.
|
||||
pick_transport() {
|
||||
_h=$1
|
||||
case ${RCLAUDE_TRANSPORT:-auto} in
|
||||
ssh) echo ssh; return ;;
|
||||
mosh) echo mosh; return ;;
|
||||
esac
|
||||
if ! command -v mosh >/dev/null 2>&1; then echo ssh; return; fi
|
||||
_cache="/tmp/rclaude-transport.$(whoami).$(printf %s "$_h" | tr -c 'A-Za-z0-9' '_')"
|
||||
if [ -s "$_cache" ]; then cat "$_cache"; return; fi
|
||||
if _probe_on "$_h" 'command -v mosh-server >/dev/null 2>&1' | grep -q . \
|
||||
|| _probe_on "$_h" 'command -v mosh-server' >/dev/null; then
|
||||
echo mosh > "$_cache"; echo mosh
|
||||
else
|
||||
echo ssh > "$_cache"; echo ssh
|
||||
fi
|
||||
}
|
||||
|
||||
# Run the triage helper on <host> with the supplied extra args. Stdout is the
|
||||
# raw TSV emitted by _claude-triage (one row per session).
|
||||
list_triage_on() {
|
||||
|
|
@ -488,7 +603,7 @@ cmd_resume() {
|
|||
# Re-sort globally by (priority desc, mtime desc) so the top of the
|
||||
# picker is the actual highest priority across the fleet.
|
||||
_triage=$(scan_hosts | while IFS= read -r h; do list_triage_on "$h"; done \
|
||||
| sort -t"$(printf '\t')" -k4,4nr -k9,9nr)
|
||||
| sort -t"$(printf '\t')" -k4,4n -k9,9nr)
|
||||
_t_count=0
|
||||
[ -n "$_tmux" ] && _t_count=$(printf '%s\n' "$_tmux" | wc -l | tr -d ' ')
|
||||
[ -n "$_triage" ] && _d_total=$(printf '%s\n' "$_triage" | wc -l | tr -d ' ')
|
||||
|
|
@ -566,6 +681,9 @@ cmd_resume() {
|
|||
_R=$(printf '\033[0m')
|
||||
_Chost=$(printf '\033[36m'); _Ctmux=$(printf '\033[1;32m')
|
||||
_Cdim=$(printf '\033[2m'); _Ckey=$(printf '\033[1;35m')
|
||||
# Triage uses P0=critical / P4=abandonable (P0 incident convention).
|
||||
# _Cp5 / _Cp4 names are kept for diff size; what they color is the
|
||||
# *top two* priority levels, whatever the scale is.
|
||||
_Cp5=$(printf '\033[1;31m'); _Cp4=$(printf '\033[33m')
|
||||
_Cblk=$(printf '\033[31m'); _Cwait=$(printf '\033[33m')
|
||||
_Cinp=$(printf '\033[36m'); _Cdone=$(printf '\033[32m')
|
||||
|
|
@ -577,7 +695,7 @@ cmd_resume() {
|
|||
# same kind. Kind is now encoded by row shape: tmux has a ▶ marker,
|
||||
# triage shows P<n> + status, session shows the snippet plain.
|
||||
_fmt_row='
|
||||
function prio_c(p) { if (p=="5") return c_p5; if (p=="4") return c_p4; return "" }
|
||||
function prio_c(p) { if (p=="0") return c_p5; if (p=="1") return c_p4; return "" }
|
||||
function stat_c(s) {
|
||||
if (s=="blocked") return c_blk
|
||||
if (s=="waiting_on_user") return c_wait
|
||||
|
|
@ -700,6 +818,10 @@ cmd_resume() {
|
|||
if is_local "$_host"; then
|
||||
exec tmux attach -t "$_target"
|
||||
else
|
||||
setup_host "$_host"
|
||||
if [ "$(pick_transport "$_host")" = "mosh" ]; then
|
||||
exec mosh "$_host" -- tmux attach -t "$_target"
|
||||
fi
|
||||
exec ssh -t $_SSH_LIVE_OPTS "$_host" tmux attach -t "$_target"
|
||||
fi
|
||||
;;
|
||||
|
|
@ -765,11 +887,33 @@ cmd_version() {
|
|||
fi
|
||||
}
|
||||
|
||||
cmd_setup() {
|
||||
# Args:
|
||||
# (none) → install on every host in scan_hosts
|
||||
# <host> [<host>...] → install on each named host
|
||||
# --on <host> → install on a single host (parity with `resume --on`)
|
||||
_hosts=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--on) shift; _hosts="$_hosts $1"; shift ;;
|
||||
--on=*) _hosts="$_hosts ${1#--on=}"; shift ;;
|
||||
*) _hosts="$_hosts $1"; shift ;;
|
||||
esac
|
||||
done
|
||||
if [ -z "$_hosts" ]; then
|
||||
scan_hosts | while IFS= read -r h; do setup_host "$h" force; done
|
||||
else
|
||||
for h in $_hosts; do setup_host "$h" force; done
|
||||
fi
|
||||
}
|
||||
|
||||
case ${1:-} in
|
||||
list) shift; cmd_list "$@"; exit ;;
|
||||
resume) shift; cmd_resume "$@"; exit ;;
|
||||
triage) shift; cmd_triage "$@"; exit ;;
|
||||
setup) shift; cmd_setup "$@"; exit ;;
|
||||
-v|--version) cmd_version; exit ;;
|
||||
-h|--help|help) cmd_help; exit ;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -912,10 +1056,16 @@ if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$host" "test -d ${dir}" 2>/dev/nu
|
|||
fi
|
||||
fi
|
||||
|
||||
setup_host "$host"
|
||||
sync_tmux_conf "$host"
|
||||
inner=$(build_inner "$dir")
|
||||
# `new-session -A` attaches if a session of that name already exists, so
|
||||
# re-running rclaude after a broken pipe lands you back in the same tmux
|
||||
# session instead of erroring with "duplicate session". Combined with
|
||||
# _SSH_LIVE_OPTS this tolerates short network drops without losing work.
|
||||
# Mosh is preferred when available (handles sleep/roam/long blips natively);
|
||||
# falls back to ssh+keepalives otherwise.
|
||||
if [ "$(pick_transport "$host")" = "mosh" ]; then
|
||||
exec mosh "$host" -- tmux new-session -A -s "${session}" "${inner}"
|
||||
fi
|
||||
exec ssh -t $_SSH_LIVE_OPTS "$host" "tmux new-session -A -s '${session}' \"${inner}\""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue