Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,19 @@ Everything here needs hardware or a session this branch was written without.

## Open — older, still true

- **Never exercised, still:** `StopFailure` · `Elicitation` ·
permission prompts in the terminal CLI · alert stack compression · click-to-focus
for anything other than VS Code.
- **Never exercised, still:** `StopFailure` · `Elicitation` · alert stack
compression · click-to-focus for anything other than VS Code.
- **Permission prompts in the terminal CLI: verified 2026-08-20.** A CLI session
on `gl-login4` was made to edit a tracked source file, `Notification` fired with
`notification_type=permission_prompt`, the event reached `events.log`, and the
Mac drew the 🔑 alert. That is release 2's one distinct capability — the chat
panel has no event for this — confirmed end to end for the first time.

**Two earlier attempts failed and both were the test's fault, not the code's.**
`uptime` and `touch /tmp/…` are auto-approved by Claude Code without raising a
permission event at all, so nothing fired and the run looked like a tailbell
bug. Anyone re-testing this must pick an action Claude Code will genuinely
stop on — editing a tracked file works.
- The prototype this grew out of still sits in `~/.claude/hooks/` (`cc-notify.sh`,
`cc-hammerspoon.lua`, `gl-listen.py`, …). It is dead code on a live machine —
easy to edit by mistake. One part of it is **not** dead: `probe.sh` is still
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ would be wrong.
| --- | --- | --- | --- |
| 0 | `v0-ssh-vscode-chatbox` | Claude Code over SSH in the VS Code chat panel → notification on macOS. Zero third-party software or service. | ✅ done |
| 1 | `v1-rich-presentation` | Centered, clickable overlay: close button, fade in/out, click to focus the originating editor window. Hammerspoon as an **optional** dependency; release 0's zero-dependency path stays the guarantee. | small |
| 2 | `v2-ssh-terminal-cli` | Claude Code CLI in an SSH terminal. Mostly already working — the addition is `Notification`, which fires in the CLI and covers permission prompts that the chat panel cannot report. | small |
| 2 | `v2-ssh-terminal-cli` | Claude Code CLI in an SSH terminal. The addition is `Notification`, which fires in the CLI and covers permission prompts the chat panel cannot report. **Verified end to end 2026-08-20** — a permission prompt on `gl-login4` drew the 🔑 alert on the Mac. | ✅ done |
| 3 | `v3-ssh-any-frontend` | Any editor, terminal or MCP frontend over SSH. Largely documentation and a test matrix rather than new code, for the same reason: hooks are process-level. | small |
| 4 | `v4-local-and-remote` | Same behaviour whether Claude Code runs on the cluster or on the Mac. The local path already exists; this release is about making one install cover both without branching config. | medium |
| 5 | `v5-claude-and-codex` | Codex as well as Claude Code. **First real architectural step:** an adapter layer, because each agent's event model differs. Needs a stable internal event schema first. | large |
Expand Down
19 changes: 19 additions & 0 deletions tests/fake-desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Stands in for anything that would reach the user's actual screen or speakers:
# osascript, afplay, say. Records the call and returns success.
#
# This exists because the suite drew real alerts on a real Mac. On the cluster
# where most of it was written, the desktop sink is unreachable and every macOS
# path is dead code, so nothing was visible; on a Mac the same cases pop an
# overlay and play a sound at whoever is running the tests. A recorder here is
# the same seam TAILBELL_CURL already provides for the network.
#
# FAKE_DESKTOP_RECORD names the log; without it the call is simply swallowed.
set -u
if [ -n "${FAKE_DESKTOP_RECORD:-}" ]; then
{ printf 'CALL|%s|' "$(basename "$0")"
for a in ${@+"$@"}; do printf '%s ' "$a"; done
printf '\n'
} >> "$FAKE_DESKTOP_RECORD" 2>/dev/null
fi
exit 0
36 changes: 36 additions & 0 deletions tests/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export TAILBELL_LOG="$TMP/events.log"
export TAILBELL_DEBUG_LOG="$TMP/debug.log"
export TAILBELL_STATE_DIR="$TMP/state"
export TAILBELL_MIN_SECONDS=60
# Nothing in this suite may reach the screen or the speakers. On the cluster that
# was free -- every macOS path is dead code there -- so it went unnoticed until
# the suite was run on a real Mac and popped overlays at the person running it.
# Same seam as TAILBELL_CURL, for the same reason.
export TAILBELL_OSASCRIPT="$REPO/tests/fake-desktop"
export TAILBELL_AFPLAY="$REPO/tests/fake-desktop"
export TAILBELL_SAY="$REPO/tests/fake-desktop"
export TAILBELL_RENDERER="osascript" # never probe for a live Hammerspoon
export FAKE_DESKTOP_RECORD="$TMP/desktop.log"
mkdir -p "$TAILBELL_STATE_DIR"
: > "$TAILBELL_LOG"

Expand Down Expand Up @@ -392,6 +401,15 @@ export TAILBELL_CHANNELS="file"
########################################################################
echo
echo "── 声音与语音 ──"
# The seams must be pointed at the recorder for the whole run, not just inside
# the cases below. Without this the suite passes identically on the cluster and
# draws real overlays on a Mac -- which is exactly how it shipped.
if [ "$TAILBELL_OSASCRIPT" = "$REPO/tests/fake-desktop" ] \
&& [ "$TAILBELL_AFPLAY" = "$REPO/tests/fake-desktop" ] \
&& [ "$TAILBELL_SAY" = "$REPO/tests/fake-desktop" ] \
&& [ -x "$REPO/tests/fake-desktop" ]; then
ok "屏幕与扬声器的三个出口全程指向录音器"
else no "有出口没被拦住 —— 在 Mac 上跑会弹真弹窗"; fi
#
# tailbell-show is macOS code, and this suite runs on the cluster. What IS
# testable here is every decision it makes before touching a Mac API: which
Expand Down Expand Up @@ -674,12 +692,30 @@ else no "install.sh 没生成 cluster-id —— 监听器会静默跳过这台
# point with no coverage at all. Cloned from this working tree at this commit, so
# it needs no network either.
OH="$TMP/onelinerhome"; mkdir -p "$OH"
# ⚠️ The one-liner branches on `uname`, and on Darwin it runs mac/install.sh,
# which writes a LaunchAgent and `launchctl load`s it. Run unguarded on a Mac
# this test installed a listener into the tester's own login session, pointed at
# this throwaway HOME. The temp directory was later cleaned; the loaded job was
# not, and went on drawing test fixtures onto a real screen for days.
#
# So the machine is told it is Linux for this one call. What is under test here
# is the agent side the one-liner installs -- the workstation installer has its
# own coverage and must never be executed by the suite.
mkdir -p "$TMP/unamebin"
printf '#!/bin/sh\necho Linux\n' > "$TMP/unamebin/uname"
chmod +x "$TMP/unamebin/uname"
HOME="$OH" TAILBELL_REPO="$REPO" TAILBELL_DIR="$OH/tailbell" \
TAILBELL_BRANCH="$(git -C "$REPO" rev-parse HEAD)" \
PATH="$TMP/unamebin:$PATH" \
bash "$REPO/packaging/get-tailbell.sh" >/dev/null 2>&1
if [ -s "$OH/.tailbell/cluster-id" ] && [ -x "$OH/tailbell/bin/tailbell-notify" ]; then
ok "一行安装脚本能装出一个完整的 agent 侧"
else no "一行安装脚本没装成"; fi
# The bug this guards: a LaunchAgent loaded into the tester's session, pointing
# at a temp directory, outliving both the test and the directory.
if [ -z "$(ls -A "$OH/Library/LaunchAgents" 2>/dev/null)" ]; then
ok "并且没有往用户会话里装 LaunchAgent"
else no "测试装了一个 LaunchAgent —— 它会活过这次测试"; fi
# And that what it installed actually notifies, which is the claim being made.
s=$(sid o001)
mkdir -p "$OH/.tailbell/state"; echo $(( $(date +%s) - 400 )) > "$OH/.tailbell/state/$s.start"
Expand Down
Loading