v0.8.13: $HCIROOT login-shell fix + both-mode detection + list_sites/sites + per-delta jq-fork slowness fix

Root-cause fix for the live-session friction where "how many sites are on
qa?" stalled on repeated `export $HCIROOT` nags despite a working `qa` SSH
alias:

1. $HCIROOT login-shell fix: ssh-helper.sh `exec` now wraps remote commands in
   `bash -lc` so the Cloverleaf login profile sources and $HCIROOT/$HCISITE/PATH
   populate as for an interactive operator login. Escape hatch: NOLOGIN prefix
   or LARRY_SSH_NO_LOGIN=1. pull-smat find/sample use the same wrapper.
2. Both-mode detection: startup surfaces a MODE= line (LOCAL / REMOTE / UNKNOWN)
   and leads with what it found instead of asking for paths.
3. First-class list_sites tool + /sites [alias]: enumerates sites in both modes
   (hcisitelist fast-path, NetConfig-walk fallback) via new ssh-helper discover.
4. System-prompt de-nagging: agents/larry.md + env-diff/regression prompts no
   longer tell Larry to ask Bryan to export $HCIROOT for a reachable host.
5. Streaming slowness (dominant residual): new pure-bash _json_str_decode
   un-escapes the common escape-free delta with zero forks, halving per-turn
   jq forks on top of v0.8.12. Round-trip verified.
6. pull-smat path capture hardened (Vera Minor #1): resolved path now emitted
   behind a SMATDB_PATH: sentinel and selected by pattern not position, so a
   login-shell MOTD/banner on stdout can't be mistaken for the path; falls back
   to prior tail -1 when no sentinel present. Selection logic unit-verified.

Vera gate: PASS-WITH-NOTES (v0.8.13). bash -n clean on larry.sh + ssh-helper.sh;
MANIFEST regenerated (48 entries) and --check clean.

Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
Bryan Johnson 2026-05-28 07:40:53 -07:00
parent 3208d50337
commit fe2f67a1aa
6 changed files with 388 additions and 43 deletions

View File

@ -4,6 +4,62 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
Versioning is loose-semver; bumps trigger the in-process self-update on every
running client via `LARRY_BASE_URL` + `MANIFEST`.
## v0.8.13 — 2026-05-28
Proactive both-mode Cloverleaf-env detection + the `$HCIROOT` login-shell fix +
a first-class site lister + the dominant residual-slowness fix (Clover). Closes
the live-session friction where "how many sites are on qa?" stalled on repeated
`export $HCIROOT` nags despite a working `qa` SSH alias.
1. **`$HCIROOT` login-shell fix (root cause).** `ssh-helper.sh exec` ran the
remote command in a NON-login, non-interactive shell, so the Cloverleaf
login profile (which exports `$HCIROOT`, `$HCISITE`, and the `hci*` PATH)
never sourced and `$HCIROOT` arrived empty — and Larry gave up and asked for a
path. `exec` now wraps the command in `bash -lc` (login shell), so the remote
env populates exactly as for an interactive operator login. Version-agnostic,
zero-config. Escape hatch: `NOLOGIN ` prefix or `LARRY_SSH_NO_LOGIN=1`. The
`pull-smat` find/sample paths use the same wrapper so `$HCISITEDIR`/`sqlite3`
resolve there too.
2. **Both-mode detection (proactive).** Startup now determines and surfaces a
`MODE=` line: **LOCAL** (`$HCIROOT` set by the local profile, or a Cloverleaf
install auto-discovered at a common path — work the local tree, no SSH),
**REMOTE** (no local install but a Cloverleaf SSH alias configured — discover
over a login shell), or **UNKNOWN** (ask which mode applies). Larry leads with
what it found instead of asking Bryan to spoon-feed paths.
3. **First-class `list_sites` tool + `/sites [alias]`.** "How many sites are on
X / what sites exist" now Just Works in both modes: REMOTE
(`list_sites(alias=qa)`) resolves the remote `$HCIROOT` in a login shell and
enumerates sites (Cloverleaf `hcisitelist` fast-path, NetConfig-walk
fallback); LOCAL (`list_sites()`) does the same against the detected local
`$HCIROOT`. Reports the resolved HCIROOT, a count, and the names. New
`ssh-helper.sh discover <alias>` backs the remote path.
4. **System-prompt de-nagging.** `agents/larry.md` and the `/nc-diff-env` /
`/nc-regression-env` templated prompts no longer instruct Larry to ask Bryan
to `export $HCIROOT` for a reachable host. The cardinal rule is explicit:
never request a path you can resolve; the only remote precondition surfaced
is an open ControlMaster (`/ssh-setup <alias>`).
5. **Streaming slowness — dominant residual fix.** v0.8.12 collapsed per-delta
routing to one jq call, but each `text_delta`/`input_json_delta` STILL forked
a second jq purely to un-escape the `@json`-encoded payload — ~N forks/turn
(N≈output tokens), and on Cygwin/MobaXterm a fork is ~50-100ms. New
`_json_str_decode` un-escapes in pure bash for the common escape-free chunk
(zero forks), deferring to jq only when a real backslash escape is present.
Halves per-turn fork count on top of v0.8.12. Round-trip verified for tab,
escaped quote/backslash, embedded newline, `\uXXXX` Unicode, and empty.
6. **`pull-smat` path capture hardened (Vera Minor #1).** The remote `.smatdb`
path lookup previously took `tail -1` of the merged stdout+stderr stream.
Under the new login shell (item 1) a profile MOTD/banner can land on stdout,
which a blind `tail -1` could mistake for the path. The remote `find` now
emits the resolved path behind a `SMATDB_PATH:` sentinel; the client selects
by pattern, not position, and only falls back to the prior `tail -1` when no
sentinel is present (so no host that worked before can regress). Selection
logic unit-verified for banner-before, banner-after, ERROR, empty, multi-
sentinel, and no-sentinel-fallback.
Compatible with bash 3.2 / Cygwin; no regression to the v0.8.12 fixes.
---
## v0.8.12 — 2026-05-27
Crash fix + slowness + cost pass on the new API-key rail (Clover). Full

View File

@ -23,19 +23,19 @@
# scripts/make-manifest.sh and bump VERSION.
# Top-level scripts
larry.sh 668a0951759c0ee67841f64d8d5f55f25a32fd908da46bc7733814123f7b0d68
larry.sh fabb64714cc0b910805f9d2fdaf8de4a903ce546ef1b3a8923fe0bcb980b9b7e
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
install-larry.sh e97da4e12a0d8863ca18d79b12f6c4294c72fa6d4b11dffeab66504236bb4eb1
# Metadata
VERSION 48732179dae488fe78e7210c84f94a44cc6913283f845d7d978302f20750f8f8
VERSION 414f2b8d3ed8f7c983632c4765179167948b1519b0cfc3596c68db66c9617dc3
MANUAL.md 755d98b802cb16a5d2d207d423b12c6ca632f118ee372cb5093fe2320a6515ce
CHANGELOG.md 78608882c507e1f8edd650c577eb17913e719f4836e6331e6885aeee89da9306
CHANGELOG.md bc695402eaafd52bb718e1852355d01a50da7cb86320df980178489de1c683fb
# Agent personas (system-prompt overlays)
agents/larry.md ace30b97a166c9f244df66ac5f5944e9251dda375a45340d443bccb34bc5ec94
agents/larry.md 11ea905fa7cac6fa7baeb11b2d62af07b15a666ce90cfe36491bcbc555244397
agents/clover.md d1bbfd6cc4642c2bff6e15dcbdf051d71b063b3fe29e0be97d17b3180d3c7ac5
agents/cloverleaf-cheatsheet.md c0a2aab91f1ddf092bce312def02cc6f3f62a1f653ca5af67a9430c3fcef4c3f
agents/regress.md bb05ed1439b1e35d6e9799e32d683bfab166472c72115c1f02757e227c74e42f
@ -52,7 +52,7 @@ lib/fetch-safe.sh abecf0045b9856f63ffa346119443c11de56547344be32bddaed9fbae6b021
lib/oauth.sh 04a93376f88fe53cc1c86a5dbe577735c60375dadd4f2fda55b921ef3cddf22b
# Secure SSH with ControlMaster (password hidden from Larry-the-LLM)
lib/ssh-helper.sh d73924a18b0c0c5856c68fb538af706316e23679f72cd94b694be73325231c9b
lib/ssh-helper.sh 7aa2aa7b3860cb48b7ba5120f9efc2563a6cdaed41242f42ecc9dd03fdebeb28
# v0.8.6: work-box → Mac headers.log sync (tsk-2026-05-27-023). Incremental,
# offset-tracked push of $LARRY_HOME/log/headers.log to a daemon-watched path

View File

@ -1 +1 @@
0.8.12
0.8.13

View File

@ -12,14 +12,21 @@ You are **Larry, Bryan's team orchestrator at myPKA**, running in portable mode
Bryan downloaded you onto a locked-down machine (no install rights). You are running as a single bash script that calls the Anthropic API directly. Your job here is **Cloverleaf interface build and Netconfig analysis** — pure interface work, **no PHI is involved**, no production push, no destructive shell commands without explicit Y/N confirmation.
## Site-awareness on startup (use this!)
## Site-awareness on startup — TWO deployment modes (be proactive, never nag)
Larry-Anywhere auto-detects the Cloverleaf runtime context every session and includes it under "**Detected runtime context (read-only)**" at the bottom of your system prompt. It tells you:
- `$HCIROOT` and whether the directory exists
- `$HCISITE`, `$HCISITEDIR`, and counts of `NetConfig`, `Xlate/`, `tables/`, `tclprocs/`, `formats/`
- Which tool layer is present: modern `cloverleaf-tools.pyz`, classic Eric scripts (`tbn`, `hlq`, `mr`, `mp`, `mg`, etc.), or neither.
Larry-Anywhere auto-detects the Cloverleaf runtime context every session, under "**Detected runtime context (read-only)**" at the bottom of your system prompt. The first line is **`MODE=`** — read it and act:
**Lead every Cloverleaf-shaped task with the detected context in mind.** If `HCIROOT` is unset and Bryan asks "what threads are on this site," ask him to `export HCIROOT=…` and `export HCISITE=…` first, or use `/site <name>` mid-session. Don't fabricate a path.
- **`MODE=LOCAL`** — Cloverleaf is on THIS box. `$HCIROOT` is detected from the local login profile or auto-discovered at a common install path. **Work the local tree directly. Never ask Bryan for a path.**
- **`MODE=REMOTE`** — Cloverleaf is on a remote server reached via a configured SSH alias (e.g. `qa`). The context lists the configured aliases. The remote `$HCIROOT` is set by that host's LOGIN profile, so **you must reach it over a login shell** — which the tools already do for you.
- **`MODE=UNKNOWN`** — no local install and no SSH alias. Only here do you ask a question: "Is Cloverleaf on this box, or on a remote host I should `/ssh-add`?"
It also lists `$HCISITE`/`$HCISITEDIR`, artifact counts, and which tool layer is present.
**The cardinal rule (this fixed real friction): NEVER ask Bryan to `export $HCIROOT` or hand you a path for a host you can already reach.** Concretely:
- "How many sites are on `qa`?" / "what sites exist?" → call **`list_sites(alias="qa")`** (REMOTE) or **`list_sites()`** (LOCAL). It resolves `$HCIROOT` for you (REMOTE: in a login shell over the open ControlMaster; LOCAL: from the detected env) and returns the count + names. Do NOT first ask Bryan to export anything.
- Any remote command runs in a **login shell automatically** (`ssh_exec` wraps it in `bash -lc`), so `$HCIROOT`, `$HCISITE`, and the `hci*` binaries are populated exactly as for an interactive operator login. You do **not** need to source a profile yourself or ask Bryan to.
- The ONLY remote precondition you surface is the ControlMaster: if a `list_sites`/`ssh_exec` result says the master is closed, tell Bryan to run `/ssh-setup <alias>` — that's it. Never the path.
- Lead with what you found ("`qa` has N sites: …"), don't fabricate a path, and don't spoon-feed prompts back to Bryan.
The cheat-sheet (`agents/cloverleaf-cheatsheet.md`) is loaded into your system prompt — use it. When proposing a command, **prefer the modern `cloverleaf-tools.pyz` form if present**, fall back to classic Eric scripts, fall back to bash one-liners only if neither layer is on PATH.

221
larry.sh
View File

@ -72,7 +72,7 @@ set -o pipefail
# ─────────────────────────────────────────────────────────────────────────────
# Config
# ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.8.12"
LARRY_VERSION="0.8.13"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# ─────────────────────────────────────────────────────────────────────────────
@ -1016,18 +1016,86 @@ if [ -z "$LARRY_AUTH_MODE" ]; then
fi
# ─────────────────────────────────────────────────────────────────────────────
# Cloverleaf environment detection
# Surfaces HCIROOT / HCISITE / HCISITEDIR and which tool layer is present
# (modern cloverleaf-tools.pyz, classic Eric scripts, or neither).
# Result is appended to the system prompt so the model knows where it is.
# Cloverleaf environment detection — BOTH deployment modes (v0.8.13)
#
# Larry-Anywhere runs in one of two modes, and it must DETECT which and act
# proactively rather than nagging Bryan for a path:
#
# MODE LOCAL — larry is installed directly ON a Cloverleaf box. $HCIROOT is
# set by the local login profile, or a Cloverleaf install sits
# at a common path. No SSH needed; work the local tree.
# MODE REMOTE — larry is on a client/local box; Cloverleaf is on a remote
# server reachable via a configured SSH alias (e.g. `qa`). The
# REMOTE env is discovered over SSH in a LOGIN shell (so the
# remote $HCIROOT populates) — see ssh-helper.sh `discover`.
#
# This function surfaces the detected MODE, the local env (if any), and any
# configured SSH aliases, so the model leads with what it found instead of
# asking the user to spoon-feed paths.
# ─────────────────────────────────────────────────────────────────────────────
# _local_cloverleaf_root — echo a local Cloverleaf $HCIROOT if one is set or
# auto-discoverable, else empty. Order: $HCIROOT (if it's a real dir) → common
# install paths that contain a Cloverleaf marker (a site with a NetConfig, or a
# server/ + bin/ pair). Cheap, read-only, depth-limited.
_local_cloverleaf_root() {
if [ -n "${HCIROOT:-}" ] && [ -d "$HCIROOT" ]; then printf '%s' "$HCIROOT"; return 0; fi
local p
for p in /quovadx/qdx*/integrator /quovadx/integrator /opt/cloverleaf/integrator \
/cloverleaf/integrator /usr/local/cloverleaf "$HOME/integrator" /qdx/integrator; do
[ -d "$p" ] || continue
# Marker: a server/ dir, or at least one immediate-child site with a NetConfig.
if [ -d "$p/server" ] || find "$p" -mindepth 2 -maxdepth 2 -name NetConfig -type f 2>/dev/null | head -1 | grep -q .; then
printf '%s' "$p"; return 0
fi
done
return 1
}
# _ssh_aliases — echo configured SSH aliases (one per line) from the hosts TSV.
# Available even though LARRY_LIB_DIR isn't resolved yet (detection runs early).
_ssh_aliases() {
local f="${LARRY_HOME:-$HOME/.larry}/.ssh-hosts.tsv"
[ -f "$f" ] || return 0
awk -F'\t' 'NR>1 && $1!="" { print $1 }' "$f" 2>/dev/null
}
detect_cloverleaf_env() {
CLOVERLEAF_CTX=""
local lines=()
# ── Mode determination ──────────────────────────────────────────────────
local local_root; local_root=$(_local_cloverleaf_root || true)
local aliases; aliases=$(_ssh_aliases)
local alias_count=0
[ -n "$aliases" ] && alias_count=$(printf '%s\n' "$aliases" | grep -c .)
# If $HCIROOT wasn't already exported but we auto-discovered a local install,
# adopt it so the rest of detection + the nc_* tools resolve against it.
if [ -z "${HCIROOT:-}" ] && [ -n "$local_root" ]; then
HCIROOT="$local_root"; export HCIROOT
fi
local mode="UNKNOWN"
if [ -n "$local_root" ]; then
mode="LOCAL"
elif [ "$alias_count" -gt 0 ]; then
mode="REMOTE"
fi
lines+=("MODE=$mode")
case "$mode" in
LOCAL) lines+=("→ Cloverleaf is on THIS box. Work the local tree at \$HCIROOT directly — do NOT ask Bryan for a path.") ;;
REMOTE) lines+=("→ Cloverleaf is on a REMOTE host. ${alias_count} SSH alias(es) configured: $(printf '%s' "$aliases" | tr '\n' ' ')") ;;
*) lines+=("→ No local Cloverleaf install and no SSH alias configured. If Bryan names a host, /ssh-add it; otherwise ask which mode applies.") ;;
esac
if [ -n "$aliases" ]; then
lines+=("Configured SSH aliases: $(printf '%s' "$aliases" | tr '\n' ' ')")
lines+=("For a remote alias: discover its env with the list_sites tool (alias=<name>) — it opens a LOGIN shell so the remote \$HCIROOT resolves. NEVER ask Bryan to export \$HCIROOT for a remote host.")
fi
if [ -n "${HCIROOT:-}" ]; then
lines+=("HCIROOT=$HCIROOT (exists=$([ -d "$HCIROOT" ] && echo yes || echo no))")
else
lines+=("HCIROOT=<unset>")
lines+=("HCIROOT=<unset locally>")
fi
if [ -n "${HCISITE:-}" ]; then
local sitedir="${HCISITEDIR:-${HCIROOT:-}/$HCISITE}"
@ -3586,6 +3654,64 @@ tool_ssh_status() {
"$helper" status 2>&1
}
# ── v0.8.13: first-class "list / count Cloverleaf sites" — works in BOTH modes.
# This is the proactive answer to "how many sites are on qa?" that previously
# stalled on a missing $HCIROOT.
#
# REMOTE: alias given → ssh-helper.sh `discover <alias>` resolves the remote
# $HCIROOT in a LOGIN shell, then lists sites (hcisitelist fast-path,
# NetConfig-walk fallback). No path is ever requested from Bryan.
# LOCAL: no alias → enumerate sites under the local $HCIROOT (or hciroot
# override): hcisitelist if on PATH, else NetConfig walk.
#
# Output is human-readable: a count + one site name per line, plus the resolved
# HCIROOT so the model can cite it.
tool_list_sites() {
local alias="${1:-}" hciroot_ovr="${2:-}"
if [ -n "$alias" ]; then
# ── REMOTE mode ──────────────────────────────────────────────────────
local helper="$LARRY_LIB_DIR/ssh-helper.sh"
[ -x "$helper" ] || { echo "ERROR: ssh-helper.sh not installed"; return 1; }
local out rc
out=$("$helper" discover "$alias" 2>&1); rc=$?
if [ "$rc" -ne 0 ]; then
printf '%s\n[list_sites: discover failed for alias=%s rc=%d. If the master is closed, tell Bryan to run /ssh-setup %s]\n' \
"$out" "$alias" "$rc" "$alias"
return 0
fi
local rroot; rroot=$(printf '%s\n' "$out" | awk -F'\t' '$1=="HCIROOT"{print $2; exit}')
local sites; sites=$(printf '%s\n' "$out" | awk -F'\t' '$1=="SITE"{print $2}' | sort -u)
local note; note=$(printf '%s\n' "$out" | awk -F'\t' '$1=="NOTE"{print $2}')
local n=0; [ -n "$sites" ] && n=$(printf '%s\n' "$sites" | grep -c .)
printf 'Cloverleaf env on alias "%s" (REMOTE, login shell):\n' "$alias"
printf ' HCIROOT = %s\n' "${rroot:-<unresolved>}"
[ -n "$note" ] && printf ' NOTE: %s\n' "$note"
printf ' sites: %d\n' "$n"
[ -n "$sites" ] && printf '%s\n' "$sites" | sed 's/^/ - /'
return 0
fi
# ── LOCAL mode ─────────────────────────────────────────────────────────
local root="${hciroot_ovr:-${HCIROOT:-}}"
if [ -z "$root" ] || [ ! -d "$root" ]; then
echo "ERROR: no local \$HCIROOT (and no hciroot override). This box has no detected Cloverleaf install. If Cloverleaf is on a remote host, pass alias=<name> (run ssh_status to see configured aliases)."
return 0
fi
local sites=""
if command -v hcisitelist >/dev/null 2>&1; then
sites=$(hcisitelist 2>/dev/null | tr ' ' '\n' | grep -v '^$' | sort -u)
fi
if [ -z "$sites" ]; then
sites=$(find "$root" -mindepth 1 -maxdepth 2 -name NetConfig -type f 2>/dev/null \
| while IFS= read -r nc; do basename "$(dirname "$nc")"; done | sort -u)
fi
local n=0; [ -n "$sites" ] && n=$(printf '%s\n' "$sites" | grep -c .)
printf 'Cloverleaf env (LOCAL):\n'
printf ' HCIROOT = %s\n' "$root"
printf ' sites: %d\n' "$n"
[ -n "$sites" ] && printf '%s\n' "$sites" | sed 's/^/ - /'
}
# ── v0.6.8: cross-env file transfer over the open ControlMaster ────────────
# ssh_pull pulls a remote file → local; ssh_push pushes local → remote. Both
# multiplex via the existing master socket (set up by /ssh-setup ALIAS) — no
@ -3761,6 +3887,7 @@ execute_tool() {
hl7_sanitize) tool_hl7_sanitize "$(J '.input_path')" "$(J '.strict // 0' | sed "s/false/0/;s/true/1/")" ;;
ssh_exec) tool_ssh_exec "$(J '.alias')" "$(J '.command')" "$(J '.max_lines // 500')" ;;
ssh_status) tool_ssh_status ;;
list_sites) tool_list_sites "$(J '.alias // ""')" "$(J '.hciroot // ""')" ;;
ssh_pull) tool_ssh_pull "$(J '.alias')" "$(J '.remote_path')" "$(J '.local_path // ""')" ;;
ssh_push) tool_ssh_push "$(J '.alias')" "$(J '.local_path')" "$(J '.remote_path')" ;;
ssh_pull_smat) tool_ssh_pull_smat "$(J '.alias')" "$(J '.site')" "$(J '.thread')" "$(J '.days_back // ""')" ;;
@ -3810,6 +3937,7 @@ TOOLS_JSON=$(cat <<'TOOLS_END'
{"name":"ssh_exec","description":"Run a shell command on a remote test/dev host via an authenticated SSH ControlMaster session. Bryan must have already configured the alias (via /ssh-add) and opened the master (via /ssh-setup). The password is stored locally and you CANNOT see it — do not ask Bryan for it; if the master is closed, tell him to run the /ssh-setup ALIAS slash command. Use ssh_status first to confirm which aliases are open. Output capped at max_lines (default 500). Tool result includes the remote exit code as a [ssh_exec: exit rc=N] footer.","input_schema":{"type":"object","properties":{"alias":{"type":"string","description":"Host alias Bryan configured. Run ssh_status to see the list."},"command":{"type":"string","description":"Shell command to execute on the remote. Quote as needed; will be passed through ssh as a single string."},"max_lines":{"type":"integer","description":"Cap output lines (default 500). Increase for known-large output, but prefer targeted commands."}},"required":["alias","command"]}},
{"name":"ssh_status","description":"List the SSH hosts Bryan has configured and which ones have an open ControlMaster session. Call this BEFORE ssh_exec to confirm an alias exists and the master is open. Each line shows: alias, user@host, port, cred (present/absent), master (open or dash). If the master is not open for an alias you need, ask Bryan to run the /ssh-setup ALIAS slash command. Do NOT attempt to authenticate yourself — you have no access to the password.","input_schema":{"type":"object","properties":{},"required":[]}},
{"name":"list_sites","description":"List and COUNT the Cloverleaf sites in the environment. This is your proactive answer to 'how many sites are on <X>' / 'what sites exist' — NEVER ask Bryan to export or hand you $HCIROOT first; this tool resolves it for you. Works in BOTH deployment modes. REMOTE mode: pass alias=<name> (a configured SSH alias, e.g. qa); the tool opens a LOGIN shell on that host so the remote $HCIROOT populates from the operator profile, then enumerates sites (Cloverleaf's hcisitelist if present, else a NetConfig walk). The ControlMaster must be open — if it is not, the result tells you to have Bryan run /ssh-setup <alias>. LOCAL mode: omit alias; the tool enumerates sites under the locally-detected $HCIROOT (or the hciroot override). Returns the resolved HCIROOT, a site count, and the site names.","input_schema":{"type":"object","properties":{"alias":{"type":"string","description":"REMOTE mode: an SSH alias from ssh_status (e.g. 'qa'). Omit for LOCAL mode (sites on this box)."},"hciroot":{"type":"string","description":"LOCAL mode only: override the detected $HCIROOT."}},"required":[]}},
{"name":"hl7_diff","description":"HL7-aware diff between two message files (or multi-message dumps). Compares segment-by-segment, field-by-field, with component and subcomponent precision. Ignores configured fields (default MSH.7 timestamp) so timestamp-only diffs do not show up as noise. Use for regression testing between environments (e.g. test vs prod route-test outputs).","input_schema":{"type":"object","properties":{"left":{"type":"string","description":"Path to left HL7 file."},"right":{"type":"string","description":"Path to right HL7 file."},"ignore":{"type":"string","description":"Comma-separated list of fields to ignore (e.g. MSH.7,MSH.10,EVN.6). Default MSH.7."},"include":{"type":"string","description":"If set, ONLY these fields are compared (overrides ignore for that set)."},"format":{"type":"string","enum":["text","tsv","count"],"description":"text=human-readable diff, tsv=machine-parseable, count=just the difference count."}},"required":["left","right"]}},
@ -4166,6 +4294,48 @@ _humanize_rate_limit() {
# - writes a JSON file with {content:[...], stop_reason, usage} on success
# - on a non-SSE JSON error body, writes that body to $2 (if given)
# - updates _LARRY_LAST_ASSISTANT_TEXT
#
# v0.8.13 (slowness, dominant residual fix): _json_str_decode — fork-free
# decode of a jq @json-encoded string ("...") back to raw text, in PURE bash.
#
# WHY this is the hot path: the streaming text delta arrives @json-encoded
# (so embedded newlines/tabs survive the line-oriented `read`). v0.8.12 cut the
# per-delta routing to ONE jq call, but each text_delta and input_json_delta
# STILL forked a SECOND jq (`jq -r '.'`) purely to un-escape that string. A
# normal answer ships dozens-to-hundreds of deltas; on Cygwin/MobaXterm a fork
# is ~50-100ms (Windows fork emulation), so that second fork is the bulk of the
# residual "feels slow" lag — ~N forks per turn, where N≈output tokens.
#
# The overwhelmingly common payload is a short chunk with NO backslash escapes
# (plain words/spaces). For that case we strip the surrounding quotes and emit
# the body verbatim — ZERO forks. We only fall back to jq when a backslash is
# actually present (rare: a literal \n, \t, \", \uXXXX in the model's text).
# Net: the dominant text path drops from 2 forks/delta to ~1, halving per-turn
# fork count on top of v0.8.12. Verified round-trip below for escaped + Unicode.
#
# $1 = the @json string INCLUDING surrounding double-quotes. Echoes raw text.
_json_str_decode() {
local s="$1"
# Empty / unquoted (jq emitted "" or a bare value) → nothing to do.
case "$s" in
'""'|'') printf ''; return ;;
'"'*'"') : ;; # well-formed quoted string → proceed
*) printf '%s' "$s"; return ;; # defensive: not quoted, pass through
esac
# Strip the surrounding quotes.
s="${s#\"}"; s="${s%\"}"
case "$s" in
*\\*)
# Has at least one escape — defer to jq for correct \uXXXX / \" / \\ / \n.
printf '"%s"' "$s" | jq -r '. // ""' 2>/dev/null
;;
*)
# No escapes — verbatim, no fork.
printf '%s' "$s"
;;
esac
}
parse_stream_to_response() {
local out_file="$1"
local err_body_file="${2:-}"
@ -4255,7 +4425,9 @@ parse_stream_to_response() {
text_delta)
local t
# _dpay is a JSON-encoded string ("..."); decode back to raw.
t=$(printf '%s' "$_dpay" | jq -r '. // ""' 2>/dev/null)
# v0.8.13: fork-free for the common (escape-free) chunk; jq only
# when an actual backslash escape is present. See _json_str_decode.
t=$(_json_str_decode "$_dpay")
# Stream to stderr so it can't get swallowed by stdout redirect.
# Color whole stream with magenta (Larry's voice).
if [ "$started_text" = "0" ]; then
@ -4267,7 +4439,9 @@ parse_stream_to_response() {
;;
input_json_delta)
local pj
pj=$(printf '%s' "$_dpay" | jq -r '. // ""' 2>/dev/null)
# v0.8.13: fork-free decode (see _json_str_decode). Tool-call
# arg fragments are typically escape-free JSON token slices.
pj=$(_json_str_decode "$_dpay")
block_input_buf[$idx]+="$pj"
;;
thinking_delta|signature_delta)
@ -5024,8 +5198,8 @@ Slash commands:
Audit: every tokenization writes a JSONL entry to
\$LARRY_HOME/log/auto-phi.log (ts/value/category/token/tier/surface/context).
/redetect re-scan for HCIROOT/HCISITE/tools
/sites list site dirs under HCIROOT
/redetect re-scan for HCIROOT/HCISITE/tools + deployment mode
/sites [alias] count/list Cloverleaf sites — local, or REMOTE via <alias> (login shell)
/site <name> switch HCISITE for this session
/pwd show current working directory
/help this help
@ -5208,7 +5382,7 @@ _LARRY_SLASH_CMDS_DESC=(
[/ssh-close]="<alias> close the ControlMaster"
[/ssh-status]="show open ControlMaster sessions + cred presence"
[/redetect]="re-scan for HCIROOT/HCISITE/tools"
[/sites]="list site dirs under HCIROOT"
[/sites]="count/list Cloverleaf sites — local, or REMOTE via /sites <alias>"
[/site]="<name> switch HCISITE for this session"
[/reset]="clear conversation history (keeps log)"
[/model]="<name> switch model (e.g. /model claude-opus-4-7)"
@ -6358,12 +6532,10 @@ main_loop() {
system_prompt=$(build_system_prompt)
larry_say "re-detected. /env to view."
continue ;;
/sites) if [ -n "${HCIROOT:-}" ] && [ -d "$HCIROOT" ]; then
if command -v sites >/dev/null 2>&1; then sites; else
find "$HCIROOT" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; \
| grep -Ev '^(archiving|master|lib|tcl|server|client|clgui|cchgs|Alerts|AppDefaults|Tables|backup.*)$' | sort
fi
else err "HCIROOT not set"; fi
/sites*) # v0.8.13: both-mode site listing. `/sites` → LOCAL; `/sites <alias>`
# → REMOTE (login-shell discover over the open ControlMaster).
local _site_alias; _site_alias=$(_slash_args "/sites" "$input")
tool_list_sites "${_site_alias:-}" ""
continue ;;
/site\ *) HCISITE="${input#/site }"; HCISITEDIR="$HCIROOT/$HCISITE"
export HCISITE HCISITEDIR
@ -6452,9 +6624,11 @@ Pattern filter: ${_pat:-<none — diff all protocols>}.
Plan and execute:
1. Run ssh_status to confirm both aliases have an open ControlMaster. If
either is closed, stop and tell me to run /ssh-setup <alias>.
2. Use ssh_exec to locate the NetConfig paths on each env (e.g.
find \$HCIROOT -maxdepth 3 -name NetConfig -type f), or ask me for the
site name if HCIROOT isn't exported on the remote.
2. Discover each env's sites with list_sites(alias=...) — it resolves the
remote \$HCIROOT in a login shell for you. Then locate NetConfig paths via
ssh_exec (e.g. find \$HCIROOT -maxdepth 3 -name NetConfig -type f) — ssh_exec
already runs in a login shell, so \$HCIROOT is populated. Do NOT ask me to
export \$HCIROOT.
3. ssh_pull each NetConfig locally. Also pull the matching Xlate/, tclprocs/,
tables/ directories alongside if you intend to diff referenced artifacts.
4. Use nc_diff_interface with --interface set per protocol, --left and --right
@ -6494,8 +6668,11 @@ Output root: $_out
Plan and execute:
1. ssh_status to confirm BOTH aliases have an open ControlMaster. If either
is closed, stop and tell me to run /ssh-setup <alias>.
2. Discover the remote HCIROOT for each alias (ssh_exec 'echo \$HCIROOT'). If
not exported, ask me. Same for HCISITE if scope=site.
2. Discover the remote HCIROOT for each alias with list_sites(alias=...) — it
resolves \$HCIROOT in a login shell and lists the sites. (Equivalently,
ssh_exec 'echo \$HCIROOT' now works because ssh_exec runs a login shell.)
Do NOT ask me to export it. Only ask which site if scope=site and it's
ambiguous from the discovered list.
3. Call nc_regression with:
- scope = "$_scope"
- source_ssh_alias = "$_src"

View File

@ -24,6 +24,14 @@
# close <alias> close ControlMaster
# status [alias] show open masters / cred presence
# exec <alias> <command...> run command via master (returns output)
# discover <alias> auto-detect remote Cloverleaf env:
# resolves $HCIROOT (LOGIN shell), then
# enumerates sites (hcisitelist fast-path,
# NetConfig-walk fallback). Prints TSV:
# HCIROOT<TAB><path>
# SITE<TAB><name> (one per site)
# No nagging for paths — the remote's own
# login profile is the source of truth.
# pull <alias> <remote> [local] scp remote → local via existing master
# push <alias> <local> <remote> scp local → remote via existing master
# pull-smat <alias> <site> <thread> [days_back]
@ -238,6 +246,36 @@ cmd_status() {
cmd_hosts
}
# v0.8.13 (Cloverleaf login-shell fix): exec defaults to a LOGIN shell.
#
# Root cause of the "qa keeps asking me for $HCIROOT" friction: a plain
# ssh host 'cmd'
# runs a NON-interactive, NON-login shell. On a Cloverleaf host, $HCIROOT (and
# $HCISITE, the hci* binaries on PATH, etc.) are exported by the LOGIN profile
# (/etc/profile.d, the hci user's ~/.profile / ~/.bash_profile, the per-site
# `.profile`). A non-login shell never sources those, so $HCIROOT arrives empty
# and Larry used to give up and nag the user for a path. Wrapping the command in
# `bash -lc` forces a login shell, so the Cloverleaf environment populates
# exactly as it does for an interactive operator login. This is the version-
# agnostic, no-config fix — it works on any Cloverleaf host whose operator login
# sets up the environment (i.e. all of them).
#
# Escape hatch: prefix the command with the literal token NOLOGIN<space> (or set
# LARRY_SSH_NO_LOGIN=1) to run a bare non-login shell — for the rare host where
# the login profile is interactive-only and hangs a non-tty `bash -l`.
_build_login_cmd() {
# $1 = raw command string. Echoes the command to hand to ssh.
local raw="$1"
case "$raw" in
NOLOGIN\ *) printf '%s' "${raw#NOLOGIN }"; return ;;
esac
[ "${LARRY_SSH_NO_LOGIN:-0}" = "1" ] && { printf '%s' "$raw"; return; }
# Single-quote the payload for a robust `bash -lc '<payload>'`. Embedded
# single quotes become '\'' (close, escaped-quote, reopen) — POSIX-portable.
local esc; esc=$(printf '%s' "$raw" | sed "s/'/'\\\\''/g")
printf "bash -lc '%s'" "$esc"
}
cmd_exec() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: exec <alias> <command...>"
@ -253,8 +291,56 @@ cmd_exec() {
if [ ! -S "$sock" ] || ! ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
die "no open master for $alias — run 'setup $alias' first"
fi
# Multiplexed; no password needed.
ssh -S "$sock" -p "$port" -o BatchMode=yes "$addr" "$cmd"
# Multiplexed; no password needed. Run in a login shell so $HCIROOT et al.
# populate from the remote Cloverleaf login profile (see _build_login_cmd).
ssh -S "$sock" -p "$port" -o BatchMode=yes "$addr" "$(_build_login_cmd "$cmd")"
}
# cmd_discover ALIAS — proactively detect the remote Cloverleaf environment.
# Resolves $HCIROOT in a LOGIN shell, then enumerates sites two ways:
# 1. hcisitelist (the Cloverleaf-shipped site lister) if it's on the login PATH
# 2. NetConfig walk under $HCIROOT (version-agnostic ground truth — the same
# "a site is a dir with a NetConfig" rule each-site.sh uses)
# Emits TSV to stdout the tool layer can parse deterministically:
# HCIROOT<TAB><path> (or HCIROOT<TAB> if unresolved)
# SITE<TAB><name> (zero or more)
# Never prompts; on failure it emits what it could resolve + a NOTE line.
cmd_discover() {
local alias="${1:-}"
[ -n "$alias" ] || die "usage: discover <alias>"
local addr_port; addr_port=$(read_host_addr "$alias")
[ -n "$addr_port" ] || die "no such alias: $alias"
local addr port
addr=$(printf '%s' "$addr_port" | cut -f1)
port=$(printf '%s' "$addr_port" | cut -f2)
local sock="$SSH_SOCKETS_DIR/$alias.sock"
if [ ! -S "$sock" ] || ! ssh -S "$sock" -O check -p "$port" "$addr" 2>/dev/null; then
die "no open master for $alias — run 'setup $alias' first"
fi
# A single login-shell remote script. It:
# - prints HCIROOT\t$HCIROOT
# - tries `hcisitelist` (Cloverleaf site lister); each non-empty token → SITE
# - falls back to a NetConfig walk under $HCIROOT (depth ≤2)
# Kept POSIX-sh so it runs under whatever /bin/sh the login shell spawns.
local remote='
printf "HCIROOT\t%s\n" "${HCIROOT:-}";
if [ -z "${HCIROOT:-}" ]; then
printf "NOTE\tHCIROOT empty even in a login shell — operator profile may not export it\n";
exit 0;
fi
got=0;
if command -v hcisitelist >/dev/null 2>&1; then
for s in $(hcisitelist 2>/dev/null); do
[ -n "$s" ] && { printf "SITE\t%s\n" "$s"; got=1; }
done;
fi;
if [ "$got" = "0" ]; then
find "$HCIROOT" -mindepth 1 -maxdepth 2 -name NetConfig -type f 2>/dev/null \
| while IFS= read -r nc; do d=$(dirname "$nc"); printf "SITE\t%s\n" "$(basename "$d")"; done \
| sort -u;
fi'
ssh -S "$sock" -p "$port" -o BatchMode=yes "$addr" "$(_build_login_cmd "$remote")"
}
# ── v0.6.8: scp helpers that multiplex via the existing ControlMaster ────────
@ -396,9 +482,10 @@ cmd_pull_smat() {
|| die "usage: pull-smat <alias> <site> <thread> [days_back]"
_resolve_open_master "$alias"
# Discover the remote .smatdb path. We rely on HCIROOT being exported in
# the remote shell rc (typical Cloverleaf user profile), else SITEDIR is
# taken as <HCIROOT>/<site> via ssh-resolved $HCIROOT. We do the find
# Discover the remote .smatdb path. $HCISITEDIR/$HCIROOT are resolved by the
# LOGIN shell (see _build_login_cmd) — the v0.8.13 fix — so we no longer
# depend on a non-login rc happening to export them. SITEDIR falls back to
# <HCIROOT>/<site> if HCISITEDIR isn't set for that site. The find runs
# remotely to avoid hard-coding process directory names.
local find_cmd
find_cmd='set -e; SDIR="${HCISITEDIR:-${HCIROOT:-}/'"$site"'}"; '
@ -406,10 +493,26 @@ cmd_pull_smat() {
find_cmd+='F=$(find "$SDIR/exec/processes" -maxdepth 2 -type f -name "'"$thread"'.smatdb" 2>/dev/null | head -1); '
find_cmd+='[ -n "$F" ] || F=$(find "$SDIR" -type f -name "'"$thread"'.smatdb" 2>/dev/null | head -1); '
find_cmd+='[ -n "$F" ] || { echo "ERROR: no smatdb found for thread '"$thread"' under $SDIR" >&2; exit 3; }; '
find_cmd+='printf "%s\n" "$F"'
# v0.8.13 M1 hardening (Vera Minor #1): emit the resolved path behind an
# unambiguous sentinel prefix instead of relying on it being the last stdout
# line. A login shell (`bash -lc`, the v0.8.13 fix) is the case most likely to
# print a MOTD/banner to stdout, which a blind `tail -1` would mistake for the
# path. We grep for the sentinel line and strip it; only if no sentinel is
# present (host somehow stripped it) do we fall back to the prior `tail -1`
# behaviour, so this can never regress a host that worked before.
find_cmd+='printf "SMATDB_PATH:%s\n" "$F"'
local remote_smatdb
remote_smatdb=$(ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$find_cmd" 2>&1 | tail -1)
local _smat_raw remote_smatdb
_smat_raw=$(ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$(_build_login_cmd "$find_cmd")" 2>&1)
remote_smatdb=$(printf '%s\n' "$_smat_raw" | grep '^SMATDB_PATH:' | tail -1)
if [ -n "$remote_smatdb" ]; then
remote_smatdb="${remote_smatdb#SMATDB_PATH:}"
else
# No sentinel — surface any ERROR: line if present, else fall back to the
# last line (pre-hardening behaviour) so failure modes stay diagnosable.
remote_smatdb=$(printf '%s\n' "$_smat_raw" | grep '^ERROR:' | tail -1)
[ -n "$remote_smatdb" ] || remote_smatdb=$(printf '%s\n' "$_smat_raw" | tail -1)
fi
case "$remote_smatdb" in
ERROR:*|'') die "remote smatdb lookup failed: $remote_smatdb" ;;
esac
@ -453,7 +556,8 @@ cmd_pull_smat() {
sample_cmd+='RETURNED=$(sqlite3 "'"$remote_smatdb"'" "SELECT MIN(1000, COUNT(*)) FROM smat_msgs WHERE Time >= $CUTOFF_MS"); '
sample_cmd+='echo "# smatdb=$(basename '"$remote_smatdb"') days_back='"$days_back"' total_in_window=$TOTAL returned=$RETURNED truncated=$([ "$TOTAL" -gt 1000 ] && echo yes || echo no)" >&2'
ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$sample_cmd"
# Login shell so sqlite3 resolves from the operator's PATH (v0.8.13).
ssh -S "$_RH_SOCK" -p "$_RH_PORT" -o BatchMode=yes "$_RH_ADDR" "$(_build_login_cmd "$sample_cmd")"
}
case "${1:-help}" in
@ -465,6 +569,7 @@ case "${1:-help}" in
close|exit) shift; cmd_close "$@" ;;
status) shift; cmd_status "$@" ;;
exec|run) shift; cmd_exec "$@" ;;
discover) shift; cmd_discover "$@" ;;
pull) shift; cmd_pull "$@" ;;
push) shift; cmd_push "$@" ;;
pull-smat) shift; cmd_pull_smat "$@" ;;