From ff2b03f10dd7452811df122fa5606aa21aebb17a Mon Sep 17 00:00:00 2001 From: Tyler Longwell Date: Thu, 19 Feb 2026 13:24:55 -0500 Subject: [PATCH 1/2] Replace GOOSE_SERVER__SECRET_KEY with GOOSE_GTWALL_FILE The goose wrapper now creates the wall file and exports its path via GOOSE_GTWALL_FILE, which all delegates inherit automatically. This replaces the dependency on GOOSE_SERVER__SECRET_KEY (a server implementation detail) with an explicit, purpose-built env var that follows the same pattern as GOOSE_MOIM_MESSAGE_FILE for telepathy. Wall filenames use PID + RANDOM for uniqueness (wall--.log). Positions directories are derived from the wall path rather than a separate naming scheme. Dashboard extracts and sanitizes WALL_ID from the wall filename for screen session and portfile scoping. AGENT_SESSION_ID was considered but rejected: delegates get their own value, so it cannot scope a shared wall file. --- dashboard | 22 +++++++++++----------- goose | 10 ++++++++++ goose_gui | 12 +++++++++++- gtwall | 27 ++++++++++++++++----------- scripts/goosetown-ui | 11 ++++++++--- tests/test_dashboard.sh | 9 ++++++++- tests/test_gtwall.sh | 41 ++++++++++++++++++++--------------------- ui/js/components.js | 2 +- 8 files changed, 85 insertions(+), 49 deletions(-) diff --git a/dashboard b/dashboard index e592ca6..c1caf39 100755 --- a/dashboard +++ b/dashboard @@ -1,7 +1,7 @@ #!/usr/bin/env bash # dashboard — Launch goosetown dashboard. Idempotent. Port on stdout, noise on stderr. # -# Multi-instance safe: each goose instance (identified by GOOSE_SERVER__SECRET_KEY) +# Multi-instance safe: each goose instance (identified by GOOSE_GTWALL_FILE) # gets its own dashboard. Instances never interfere with each other — no shared screen # names, no global pkill, no "stale session" replacement. set -euo pipefail @@ -19,11 +19,13 @@ info() { echo "$*" >&2; } command -v uv &>/dev/null || die "uv not found. Install: curl -LsSf https://astral.sh/uv/install.sh | sh" # ── Instance identity ─────────────────────────────────────────────────────── -# GOOSE_SERVER__SECRET_KEY is a UUID unique per goose instance. The first 8 chars -# (WALL_ID) already scope wall files and position dirs — we reuse it to scope -# screen sessions and portfiles so multiple dashboards can coexist. -[[ -n "${GOOSE_SERVER__SECRET_KEY:-}" ]] || die2 "GOOSE_SERVER__SECRET_KEY not set" -WALL_ID="${GOOSE_SERVER__SECRET_KEY:0:8}" +# GOOSE_GTWALL_FILE points to the wall file for this goose instance. Extract the +# WALL_ID from the filename to scope screen sessions and portfiles so multiple +# dashboards can coexist. +[[ -n "${GOOSE_GTWALL_FILE:-}" ]] || die2 "GOOSE_GTWALL_FILE not set" +# Sanitize to safe chars for screen session names and portfiles (must match gtwall's rules) +WALL_ID=$(basename "$GOOSE_GTWALL_FILE" .log | sed 's/^wall-//' | tr -cd 'A-Za-z0-9_-') +[[ -n "$WALL_ID" ]] || die2 "Could not derive WALL_ID from GOOSE_GTWALL_FILE" SCREEN_NAME="goosetown-ui-${WALL_ID}" PORTFILE="${WALLS_DIR}/dashboard-${WALL_ID}.port" @@ -58,10 +60,8 @@ resolve_session() { # ── Wall resolution ───────────────────────────────────────────────────────── resolve_wall() { - local wall_file="${WALLS_DIR}/wall-${WALL_ID}.log" - mkdir -p "$WALLS_DIR" - touch "$wall_file" - echo "$wall_file" + # Use GOOSE_GTWALL_FILE directly - it's already set by the goose wrapper + echo "$GOOSE_GTWALL_FILE" } # ── Port helpers ──────────────────────────────────────────────────────────── @@ -197,7 +197,7 @@ Usage: ./dashboard [--stop|--status|--open|--help] --help This message Multiple goose instances get separate dashboards (ports 4242-4300). -Each instance is scoped by GOOSE_SERVER__SECRET_KEY. +Each instance is scoped by GOOSE_GTWALL_FILE. Exit codes: 0=ok, 1=error/not-running, 2=config-error, 3=port-exhaustion, 4=startup-timeout EOF diff --git a/goose b/goose index b46d576..b16be1a 100755 --- a/goose +++ b/goose @@ -22,12 +22,22 @@ touch "$TELEPATHY_FILE" # Export for tom extension - orchestrator and all delegates will see this export GOOSE_MOIM_MESSAGE_FILE="$TELEPATHY_FILE" +# Generate unique wall file for this session +WALLS_DIR="${HOME}/.goosetown/walls" +mkdir -p "$WALLS_DIR" +WALL_FILE="${WALLS_DIR}/wall-$$-${RANDOM}.log" +touch "$WALL_FILE" +export GOOSE_GTWALL_FILE="$WALL_FILE" + # Cleanup on exit cleanup() { rm -f "$TELEPATHY_FILE" + rm -f "$WALL_FILE" + rm -rf "${WALL_FILE%.log}.positions" } trap cleanup EXIT echo "🦆 Goosetown telepathy enabled: $TELEPATHY_FILE" >&2 +echo "🦆 Goosetown wall enabled: $WALL_FILE" >&2 exec "$GOOSE_BIN" "$@" diff --git a/goose_gui b/goose_gui index 8263299..aa56213 100755 --- a/goose_gui +++ b/goose_gui @@ -21,14 +21,24 @@ touch "$TELEPATHY_FILE" # Export for tom extension export GOOSE_MOIM_MESSAGE_FILE="$TELEPATHY_FILE" +# Generate unique wall file for this session +WALLS_DIR="${HOME}/.goosetown/walls" +mkdir -p "$WALLS_DIR" +WALL_FILE="${WALLS_DIR}/wall-$$-${RANDOM}.log" +touch "$WALL_FILE" +export GOOSE_GTWALL_FILE="$WALL_FILE" + # Cleanup on exit cleanup() { rm -f "$TELEPATHY_FILE" - echo "Telepathy file cleaned up." >&2 + rm -f "$WALL_FILE" + rm -rf "${WALL_FILE%.log}.positions" + echo "Telepathy and wall files cleaned up." >&2 } trap cleanup EXIT echo "🦆 Goosetown GUI telepathy enabled: $TELEPATHY_FILE" >&2 +echo "🦆 Goosetown GUI wall enabled: $WALL_FILE" >&2 echo "Keep this terminal open. Press Ctrl+C when done to clean up." >&2 # Launch GUI with -n to force new instance (ensures env var is picked up) diff --git a/gtwall b/gtwall index 12bb8ae..d05da42 100755 --- a/gtwall +++ b/gtwall @@ -12,25 +12,26 @@ WALL_DIR="${GTWALL_DIR:-${HOME}/.goosetown}" WALLS_DIR="${WALL_DIR}/walls" -# Use GOOSE_SERVER__SECRET_KEY as session ID - it's unique per goose session -# and shared by orchestrator + all delegates automatically -if [[ -n "${GOOSE_SERVER__SECRET_KEY:-}" ]]; then - # Use first 8 chars of the UUID for readability - SESSION_ID="${GOOSE_SERVER__SECRET_KEY:0:8}" +# Use GOOSE_GTWALL_FILE if set, otherwise fallback to default wall +if [[ -n "${GOOSE_GTWALL_FILE:-}" ]]; then + WALL_FILE="$GOOSE_GTWALL_FILE" else - # Fallback for non-goose usage - SESSION_ID="default" + # Fallback for non-goose usage (manual invocation) + mkdir -p "$WALLS_DIR" + WALL_FILE="${WALLS_DIR}/wall-default.log" fi +# Derive positions directory from wall file path +POS_DIR="${WALL_FILE%.log}.positions" + +# Extract display-friendly session ID from wall filename for help/status text +SESSION_ID=$(basename "$WALL_FILE" .log | sed 's/^wall-//') # Sanitize SESSION_ID to safe filesystem characters (alphanumeric, dash, underscore) SESSION_ID=$(printf '%s' "$SESSION_ID" | tr -cd 'A-Za-z0-9_-') if [[ -z "$SESSION_ID" ]]; then SESSION_ID="default" fi -WALL_FILE="${WALLS_DIR}/wall-${SESSION_ID}.log" -POS_DIR="${WALLS_DIR}/positions-${SESSION_ID}" - # Lock management - track if we hold the lock for cleanup LOCKFILE_HELD="" @@ -247,7 +248,11 @@ ID Rules: Session: EOF - printf ' Session ID: %s (from GOOSE_SERVER__SECRET_KEY)\n' "$SESSION_ID" + if [[ -n "${GOOSE_GTWALL_FILE:-}" ]]; then + printf ' Session ID: %s (from GOOSE_GTWALL_FILE)\n' "$SESSION_ID" + else + printf ' Session ID: %s (default - no GOOSE_GTWALL_FILE set)\n' "$SESSION_ID" + fi printf ' Wall: %s\n' "$WALL_FILE" printf ' Positions: %s/\n' "$POS_DIR" } diff --git a/scripts/goosetown-ui b/scripts/goosetown-ui index 5270602..d87d490 100755 --- a/scripts/goosetown-ui +++ b/scripts/goosetown-ui @@ -80,13 +80,18 @@ def find_parent_session(cur) -> str | None: def find_wall_file(session_key: str | None = None) -> str | None: - if session_key is None: - secret = os.environ.get("GOOSE_SERVER__SECRET_KEY", "") - session_key = secret[:8] if secret else None + # Check GOOSE_GTWALL_FILE env var first + gtwall_file = os.environ.get("GOOSE_GTWALL_FILE", "") + if gtwall_file and os.path.isfile(gtwall_file): + return gtwall_file + + # Fallback: use session_key if provided if session_key: path = os.path.join(WALLS_DIR, f"wall-{session_key}.log") if os.path.isfile(path): return path + + # Last resort: newest wall file wall_files = glob.glob(os.path.join(WALLS_DIR, "wall-*.log")) return max(wall_files, key=os.path.getmtime) if wall_files else None diff --git a/tests/test_dashboard.sh b/tests/test_dashboard.sh index 56419e6..84af8eb 100755 --- a/tests/test_dashboard.sh +++ b/tests/test_dashboard.sh @@ -6,7 +6,12 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" DASHBOARD="$SCRIPT_DIR/../dashboard" -export GOOSE_SERVER__SECRET_KEY="dash${$}${RANDOM}" +# Create unique wall file for this test session +WALLS_DIR="$HOME/.goosetown/walls" +mkdir -p "$WALLS_DIR" +WALL_FILE="${WALLS_DIR}/wall-test-dash-${$}-${RANDOM}.log" +touch "$WALL_FILE" +export GOOSE_GTWALL_FILE="$WALL_FILE" passed=0 failed=0 @@ -18,6 +23,8 @@ skip() { echo " ⊘ $1 (skipped: $2)"; ((++skipped)) || true; } cleanup() { "$DASHBOARD" --stop >/dev/null 2>&1 || true + rm -f "$WALL_FILE" 2>/dev/null || true + rm -rf "${WALL_FILE%.log}.positions" 2>/dev/null || true } trap cleanup EXIT diff --git a/tests/test_gtwall.sh b/tests/test_gtwall.sh index 7fc774b..37b489a 100755 --- a/tests/test_gtwall.sh +++ b/tests/test_gtwall.sh @@ -6,8 +6,12 @@ set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" GTWALL="$SCRIPT_DIR/../gtwall" -# Use unique session key - PID + RANDOM for uniqueness -export GOOSE_SERVER__SECRET_KEY="gt${$}${RANDOM}" +# Create unique wall file for this test session +WALLS_DIR="$HOME/.goosetown/walls" +mkdir -p "$WALLS_DIR" +WALL_FILE="${WALLS_DIR}/wall-test-${$}-${RANDOM}.log" +touch "$WALL_FILE" +export GOOSE_GTWALL_FILE="$WALL_FILE" passed=0 failed=0 @@ -16,9 +20,8 @@ pass() { echo " ✓ $1"; ((++passed)) || true; } fail() { echo " ✗ $1: $2" >&2; ((++failed)) || true; } cleanup() { - local wall_id="${GOOSE_SERVER__SECRET_KEY:0:8}" - rm -rf "$HOME/.goosetown/walls/wall-${wall_id}.log"* 2>/dev/null || true - rm -rf "$HOME/.goosetown/walls/positions-${wall_id}" 2>/dev/null || true + rm -f "$WALL_FILE" 2>/dev/null || true + rm -rf "${WALL_FILE%.log}.positions" 2>/dev/null || true } trap cleanup EXIT @@ -84,16 +87,17 @@ test_clear() { test_session_isolation() { echo "test_session_isolation" - # Keys must differ in first 8 chars - local key_a="isola${$}A" - local key_b="isolb${$}B" + # Create separate wall files for isolation test + local wall_a="${WALLS_DIR}/wall-test-isola-${$}.log" + local wall_b="${WALLS_DIR}/wall-test-isolb-${$}.log" + touch "$wall_a" "$wall_b" - GOOSE_SERVER__SECRET_KEY="$key_a" "$GTWALL" writer "secret-A" >/dev/null - GOOSE_SERVER__SECRET_KEY="$key_b" "$GTWALL" writer "secret-B" >/dev/null + GOOSE_GTWALL_FILE="$wall_a" "$GTWALL" writer "secret-A" >/dev/null + GOOSE_GTWALL_FILE="$wall_b" "$GTWALL" writer "secret-B" >/dev/null local outputA outputB - outputA=$(GOOSE_SERVER__SECRET_KEY="$key_a" "$GTWALL" reader 2>/dev/null || echo "") - outputB=$(GOOSE_SERVER__SECRET_KEY="$key_b" "$GTWALL" reader 2>/dev/null || echo "") + outputA=$(GOOSE_GTWALL_FILE="$wall_a" "$GTWALL" reader 2>/dev/null || echo "") + outputB=$(GOOSE_GTWALL_FILE="$wall_b" "$GTWALL" reader 2>/dev/null || echo "") # Each should only see its own if [[ "$outputA" != *"secret-B"* && "$outputB" != *"secret-A"* ]]; then @@ -103,22 +107,17 @@ test_session_isolation() { fi # Cleanup isolation test walls - rm -rf "$HOME/.goosetown/walls/wall-${key_a:0:8}.log"* 2>/dev/null || true - rm -rf "$HOME/.goosetown/walls/wall-${key_b:0:8}.log"* 2>/dev/null || true - rm -rf "$HOME/.goosetown/walls/positions-${key_a:0:8}" 2>/dev/null || true - rm -rf "$HOME/.goosetown/walls/positions-${key_b:0:8}" 2>/dev/null || true + rm -f "$wall_a" "$wall_b" 2>/dev/null || true + rm -rf "${wall_a%.log}.positions" "${wall_b%.log}.positions" 2>/dev/null || true } test_stale_lock_cleanup() { echo "test_stale_lock_cleanup" "$GTWALL" --clear >/dev/null 2>&1 || true - # Get correct lock path: ${WALLS_DIR}/wall-${SESSION_ID}.log.lock - local wall_id="${GOOSE_SERVER__SECRET_KEY:0:8}" - local walls_dir="$HOME/.goosetown/walls" - local lock_dir="${walls_dir}/wall-${wall_id}.log.lock" + # Get correct lock path from GOOSE_GTWALL_FILE + local lock_dir="${WALL_FILE}.lock" - mkdir -p "$walls_dir" mkdir -p "$lock_dir" # Create stale lock: diff --git a/ui/js/components.js b/ui/js/components.js index 07a290d..78816ad 100644 --- a/ui/js/components.js +++ b/ui/js/components.js @@ -221,7 +221,7 @@ function renderBulletin(st) { ? html`
${ messages.length === 0 - ? 'Waiting for wall… (start a goose session or check GOOSE_SERVER__SECRET_KEY)' + ? 'Waiting for wall… (start a goose session or check GOOSE_GTWALL_FILE)' : 'No messages match current filters.' }
` From 8d3ffa1b8a8eec6f98acdbbccbe865ef87e77b76 Mon Sep 17 00:00:00 2001 From: Tyler Longwell Date: Thu, 19 Feb 2026 13:41:00 -0500 Subject: [PATCH 2/2] Fix exec trap, unanchored sed, and replace vs removeprefix - goose: drop exec so EXIT trap fires and cleans up wall + telepathy - gtwall: anchor sed in --list to strip only the wall- prefix - goosetown-ui: use removeprefix instead of replace for wall-id derivation --- goose | 2 +- gtwall | 2 +- scripts/goosetown-ui | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/goose b/goose index b16be1a..44568d8 100755 --- a/goose +++ b/goose @@ -40,4 +40,4 @@ trap cleanup EXIT echo "🦆 Goosetown telepathy enabled: $TELEPATHY_FILE" >&2 echo "🦆 Goosetown wall enabled: $WALL_FILE" >&2 -exec "$GOOSE_BIN" "$@" +"$GOOSE_BIN" "$@" diff --git a/gtwall b/gtwall index d05da42..77a18d7 100755 --- a/gtwall +++ b/gtwall @@ -358,7 +358,7 @@ USAGE shopt -s nullglob for f in "${WALLS_DIR}"/wall-*.log; do if [[ -f "$f" ]]; then - session=$(basename "$f" .log | sed 's/wall-//') + session=$(basename "$f" .log | sed 's/^wall-//') lines=$(wc -l < "$f" | tr -d ' ') if [[ "$session" == "$SESSION_ID" ]]; then printf ' %s: %s messages (current)\n' "$session" "$lines" diff --git a/scripts/goosetown-ui b/scripts/goosetown-ui index d87d490..539f791 100755 --- a/scripts/goosetown-ui +++ b/scripts/goosetown-ui @@ -514,7 +514,7 @@ def main(): wall_file = args.wall or find_wall_file() wall_id = "" if wall_file: - wall_id = Path(wall_file).stem.replace("wall-", "") + wall_id = Path(wall_file).stem.removeprefix("wall-") print(f"📜 Wall file: {wall_file}", file=sys.stderr) else: print("⚠️ No wall file found — will poll for it", file=sys.stderr)