From 179f7709394c98b4ddb35b15769d9b59512a6155 Mon Sep 17 00:00:00 2001 From: Juan Pelaez Date: Mon, 5 Oct 2026 22:39:06 -0600 Subject: [PATCH 1/2] feat(send): ring the AI Maestro doorbell after a same-host inbox write amp-send writes a same-host recipient's inbox file directly, so a local AI Maestro never ran its wake chain. After the write, amp_ring_doorbell makes a best-effort, backgrounded, silent POST so the agent is woken now. No server, an older server or AMP_DOORBELL=0: behavior is unchanged. Co-Authored-By: Claude Sonnet 5.5 --- scripts/amp-helper.sh | 19 ++++++++ scripts/amp-send.sh | 8 ++++ tests/unit/send_doorbell.bats | 88 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/unit/send_doorbell.bats diff --git a/scripts/amp-helper.sh b/scripts/amp-helper.sh index b5f83eb..2727723 100755 --- a/scripts/amp-helper.sh +++ b/scripts/amp-helper.sh @@ -327,6 +327,25 @@ AMP_MAX_TOTAL_ATTACHMENT_SIZE="${AMP_MAX_TOTAL_ATTACHMENT_SIZE:-104857600}" # 1 # AI Maestro connection AMP_MAESTRO_URL="${AMP_MAESTRO_URL:-http://localhost:23000}" +# Tell a local AI Maestro that a message just landed in a same-host recipient's +# inbox, so it wakes the agent now instead of at its next inbox sweep. The inbox +# file is already written and stays the source of truth: this is a best-effort +# nudge. It never prints, never fails, and never waits (backgrounded, 3 s cap), +# so with no server running, or an older one, sending behaves exactly as before. +# Opt out with AMP_DOORBELL=0. +amp_ring_doorbell() { + local recipient="$1" msg_id="$2" body + [ "${AMP_DOORBELL:-1}" = "0" ] && return 0 + [ -n "$recipient" ] && [ -n "$msg_id" ] || return 0 + command -v curl >/dev/null 2>&1 || return 0 + command -v jq >/dev/null 2>&1 || return 0 + body=$(jq -nc --arg r "$recipient" --arg m "$msg_id" '{recipient:$r,messageId:$m}' 2>/dev/null) || return 0 + ( curl -s -m 3 --connect-timeout 1 -o /dev/null -X POST \ + "${AMP_MAESTRO_URL}/api/messages/doorbell" \ + -H 'Content-Type: application/json' -d "$body" >/dev/null 2>&1 & ) >/dev/null 2>&1 + return 0 +} + # Provider domain (AMP v1) AMP_PROVIDER_DOMAIN="${AMP_PROVIDER_DOMAIN:-aimaestro.local}" AMP_LOCAL_DOMAIN="${AMP_PROVIDER_DOMAIN}" diff --git a/scripts/amp-send.sh b/scripts/amp-send.sh index 6575df9..7f57b55 100755 --- a/scripts/amp-send.sh +++ b/scripts/amp-send.sh @@ -590,6 +590,10 @@ if [ "$ROUTE" = "local" ]; then echo "$DELIVERY_MSG" > "${RECIPIENT_INBOX}/${SENDER_DIR}/${MSG_ID}.json" + # Wake the recipient now if a local AI Maestro is running (best effort, silent) + + amp_ring_doorbell "${RECIPIENT_UUID:-$ADDR_NAME}" "$MSG_ID" + echo "✅ Message sent (local filesystem delivery)" echo "" echo " To: ${FULL_RECIPIENT}" @@ -743,6 +747,10 @@ if [ "$ROUTE" = "local" ]; then echo "$DELIVERY_MSG" > "${RECIPIENT_INBOX}/${SENDER_DIR}/${MSG_ID}.json" + # Wake the recipient now if a local AI Maestro is running (best effort, silent) + + amp_ring_doorbell "${RECIPIENT_UUID:-$ADDR_NAME}" "$MSG_ID" + echo "✅ Message sent (local filesystem delivery)" echo "" echo " To: ${FULL_RECIPIENT}" diff --git a/tests/unit/send_doorbell.bats b/tests/unit/send_doorbell.bats new file mode 100644 index 0000000..c9376e7 --- /dev/null +++ b/tests/unit/send_doorbell.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats +# ============================================================================= +# Tests: amp_ring_doorbell (best-effort wake after a same-host inbox write) +# ============================================================================= + +setup() { + load '../test_helper' + setup_amp_env + create_test_config + source_amp_helper + unset SCRIPT_DIR + + STUBS="${BATS_TEST_TMPDIR}/stubs" + mkdir -p "$STUBS" + cat > "${STUBS}/curl" <> "${BATS_TEST_TMPDIR}/curl.calls" +[ -n "\$CURL_STUB_SLEEP" ] && sleep "\$CURL_STUB_SLEEP" +exit "\${CURL_STUB_EXIT:-0}" +STUB + chmod +x "${STUBS}/curl" + export PATH="${STUBS}:${PATH}" + export AMP_MAESTRO_URL="http://localhost:23000" +} + +wait_for_calls() { + for _ in $(seq 1 30); do + [ -s "${BATS_TEST_TMPDIR}/curl.calls" ] && return 0 + sleep 0.1 + done + return 1 +} + +@test "rings the doorbell with the recipient and message id" { + run amp_ring_doorbell "agent-uuid-1" "msg_123_abc" + assert_success + assert_output "" + wait_for_calls + run cat "${BATS_TEST_TMPDIR}/curl.calls" + assert_output --partial "http://localhost:23000/api/messages/doorbell" + assert_output --partial '"recipient":"agent-uuid-1"' + assert_output --partial '"messageId":"msg_123_abc"' +} + +@test "prints nothing and succeeds when the server is unreachable" { + export CURL_STUB_EXIT=7 + run amp_ring_doorbell "a" "m" + assert_success + assert_output "" +} + +@test "does not wait for a slow server" { + export CURL_STUB_SLEEP=5 + start=$SECONDS + run amp_ring_doorbell "a" "m" + assert_success + [ $((SECONDS - start)) -lt 3 ] +} + +@test "AMP_DOORBELL=0 opts out" { + export AMP_DOORBELL=0 + run amp_ring_doorbell "a" "m" + assert_success + sleep 0.3 + [ ! -e "${BATS_TEST_TMPDIR}/curl.calls" ] +} + +@test "does nothing without a recipient or a message id" { + run amp_ring_doorbell "" "m" + assert_success + run amp_ring_doorbell "a" "" + assert_success + sleep 0.3 + [ ! -e "${BATS_TEST_TMPDIR}/curl.calls" ] +} + +@test "succeeds with curl missing" { + mkdir -p "${BATS_TEST_TMPDIR}/nocurl" + ln -sf "$(command -v bash)" "${BATS_TEST_TMPDIR}/nocurl/bash" + ln -sf "$(command -v jq)" "${BATS_TEST_TMPDIR}/nocurl/jq" + run env PATH="${BATS_TEST_TMPDIR}/nocurl" "${BATS_TEST_TMPDIR}/nocurl/bash" -c 'source "$0"; amp_ring_doorbell a m; echo rc=$?' "${SCRIPTS_DIR}/amp-helper.sh" + assert_output --partial "rc=0" +} + +@test "amp-send rings after both local filesystem writes" { + run grep -c 'amp_ring_doorbell "${RECIPIENT_UUID:-$ADDR_NAME}" "$MSG_ID"' "${SCRIPTS_DIR}/amp-send.sh" + assert_output "2" +} From 45e97bd76bcceb8a2490ac8ccab62c35a3fb1a15 Mon Sep 17 00:00:00 2001 From: Juan Pelaez Date: Mon, 5 Oct 2026 22:40:39 -0600 Subject: [PATCH 2/2] test: curl-missing doorbell test no longer strips PATH (breaks dirname on Linux) Co-Authored-By: Claude Sonnet 5.5 --- tests/unit/send_doorbell.bats | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/unit/send_doorbell.bats b/tests/unit/send_doorbell.bats index c9376e7..4c315ab 100644 --- a/tests/unit/send_doorbell.bats +++ b/tests/unit/send_doorbell.bats @@ -75,11 +75,14 @@ wait_for_calls() { } @test "succeeds with curl missing" { - mkdir -p "${BATS_TEST_TMPDIR}/nocurl" - ln -sf "$(command -v bash)" "${BATS_TEST_TMPDIR}/nocurl/bash" - ln -sf "$(command -v jq)" "${BATS_TEST_TMPDIR}/nocurl/jq" - run env PATH="${BATS_TEST_TMPDIR}/nocurl" "${BATS_TEST_TMPDIR}/nocurl/bash" -c 'source "$0"; amp_ring_doorbell a m; echo rc=$?' "${SCRIPTS_DIR}/amp-helper.sh" - assert_output --partial "rc=0" + # Make `command -v curl` fail without touching PATH (other tools stay available) + command() { if [ "$1" = "-v" ] && [ "$2" = "curl" ]; then return 1; fi; builtin command "$@"; } + run amp_ring_doorbell "a" "m" + unset -f command + assert_success + assert_output "" + sleep 0.3 + [ ! -e "${BATS_TEST_TMPDIR}/curl.calls" ] } @test "amp-send rings after both local filesystem writes" {