native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Shell
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121#!/bin/bash# Verify that animated tab titles stay coalesced, from outside the app.## `prowl list` reports `tab.title` from the same live snapshot the tab bar# observes, so polling it measures exactly the write rate that title coalescing# governs (docs-ai/032.004, Fix 7). Agent TUIs animate spinner glyphs into the# title at roughly 10 Hz; the shipped coalescing must hold every animating tab# at or near one visible change per second. This is the one claim from the# #644–#665 wave that a Release build can be checked against with no# instrumentation at all.## Usage: bash scripts/measure-title-coalescing.sh [duration_seconds] [max_changes_per_second]# bash scripts/measure-title-coalescing.sh 30 1.25 # default## The threshold leaves headroom over the exact 1/s contract because sampling# jitter can split one interval across two observation windows.## Environment:# PROWL_MEASURE_DIR where runs are written# (default ~/Library/Logs/Prowl/measurements)## Exit codes: 0 pass; 1 an animating tab exceeded the threshold; 2 CLI# unavailable; 3 inconclusive (no tab changed at all — nothing was animating);# 64 usage error.set -euo pipefailumask 077
DURATION=${1:-30}MAX_RATE=${2:-1.25}
usage_error() { echo "$1" >&2 exit 64}
case "$DURATION" in '' | *[!0-9]*) usage_error "duration_seconds must be a positive integer." ;;esac[ "$DURATION" -gt 0 ] || usage_error "duration_seconds must be greater than zero."case "$MAX_RATE" in '' | *[!0-9.]* | . | *.*.*) usage_error "max_changes_per_second must be a positive number." ;;esac
command -v prowl >/dev/null || { echo "prowl CLI is not on PATH." >&2; exit 2; }prowl list --json >/dev/null 2>&1 || { echo "prowl CLI cannot reach a running app." >&2; exit 2; }
OUT="${PROWL_MEASURE_DIR:-$HOME/Library/Logs/Prowl/measurements}/title-rates/$(date +%Y%m%d-%H%M%S)-$$"mkdir -p "$OUT"
# Rates are computed against measured wall-clock, not the nominal poll count:# CLI latency stretches every polling loop, and dividing by the intended# duration would overstate each tab's change rate.status=0DURATION="$DURATION" MAX_RATE="$MAX_RATE" OUT="$OUT" python3 <<'PY' || status=$?import jsonimport osimport subprocessimport sysimport time
duration = int(os.environ["DURATION"])max_rate = float(os.environ["MAX_RATE"])out = os.environ["OUT"]
poll_interval = 0.125changes = {}titles = {}last = {}polls = 0
start = time.monotonic()with open(os.path.join(out, "titles.jsonl"), "w") as log: while time.monotonic() - start < duration: result = subprocess.run(["prowl", "list", "--json"], capture_output=True, text=True) now = time.monotonic() - start try: items = json.loads(result.stdout)["data"]["items"] except (json.JSONDecodeError, KeyError): continue polls += 1 snapshot = {} for item in items: tab = item.get("tab") or {} tab_id, title = tab.get("id"), tab.get("title") if tab_id is None or title is None: continue snapshot[tab_id] = title titles.setdefault(tab_id, title) if tab_id in last and last[tab_id] != title: changes[tab_id] = changes.get(tab_id, 0) + 1 last[tab_id] = title log.write(json.dumps({"t": round(now, 3), "tabs": snapshot}) + "\n") time.sleep(poll_interval)elapsed = time.monotonic() - start
lines = [f"polls={polls} elapsed={elapsed:.1f}s threshold={max_rate}/s"]worst = 0.0for tab_id, title in titles.items(): count = changes.get(tab_id, 0) rate = count / elapsed worst = max(worst, rate) flag = " <-- OVER" if rate > max_rate else "" lines.append(f" {tab_id[:8]} changes={count:3d} rate={rate:.2f}/s {title[:60]}{flag}")
summary = "\n".join(lines)print(summary)with open(os.path.join(out, "summary.txt"), "w") as f: f.write(summary + "\n")
if polls == 0: print("no successful polls; is the app responding?", file=sys.stderr) sys.exit(2)if not changes: print("\nno tab title changed at all — nothing was animating, run is inconclusive.", file=sys.stderr) sys.exit(3)sys.exit(1 if worst > max_rate else 0)PYechoecho "run dir: $OUT"exit "$status"