diff --git a/.augment-plugin/marketplace.json b/.augment-plugin/marketplace.json index 5124f8f..266d984 100644 --- a/.augment-plugin/marketplace.json +++ b/.augment-plugin/marketplace.json @@ -15,6 +15,14 @@ "source": "./plugin_marketplace/code-review", "category": "code-quality", "tags": ["code-review", "git", "commands", "agents"] + }, + { + "name": "warp", + "description": "Native Warp notifications when Auggie completes tasks or needs input", + "version": "2.0.0", + "source": "./plugin_marketplace/warp", + "category": "productivity", + "tags": ["notifications", "terminal", "warp"] } ] } diff --git a/plugin_marketplace/warp/.augment-plugin/plugin.json b/plugin_marketplace/warp/.augment-plugin/plugin.json new file mode 100644 index 0000000..6b46b52 --- /dev/null +++ b/plugin_marketplace/warp/.augment-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "warp", + "description": "Warp terminal integration for Auggie - native notifications, and more to come", + "version": "2.0.0", + "author": { + "name": "Augment Code", + "url": "https://www.augmentcode.com" + }, + "homepage": "https://github.com/augmentcode/auggie/tree/main/plugin_marketplace/warp" +} diff --git a/plugin_marketplace/warp/hooks/hooks.json b/plugin_marketplace/warp/hooks/hooks.json new file mode 100644 index 0000000..6b2df61 --- /dev/null +++ b/plugin_marketplace/warp/hooks/hooks.json @@ -0,0 +1,70 @@ +{ + "description": "Warp terminal notifications", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-session-start.sh" + } + ] + } + ], + "Stop": [ + { + "metadata": { + "includeConversationData": true + }, + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-stop.sh" + } + ] + } + ], + "Notification": [ + { + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-notification.sh" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": ".*", + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-post-tool-use.sh" + } + ] + } + ], + "PreToolUse": [ + { + "matcher": ".*", + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-pre-tool-use.sh" + } + ] + } + ], + "PromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "${AUGMENT_PLUGIN_ROOT}/scripts/on-prompt-submit.sh" + } + ] + } + ] + } +} diff --git a/plugin_marketplace/warp/scripts/build-payload.sh b/plugin_marketplace/warp/scripts/build-payload.sh new file mode 100755 index 0000000..b6b3c93 --- /dev/null +++ b/plugin_marketplace/warp/scripts/build-payload.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Builds a structured JSON notification payload for warp://cli-agent. +# +# Usage: source this file, then call build_payload with event-specific fields. +# +# Example: +# source "$(dirname "${BASH_SOURCE[0]}")/build-payload.sh" +# BODY=$(build_payload "$INPUT" "stop" \ +# --arg query "$QUERY" \ +# --arg response "$RESPONSE") +# +# The function extracts common fields (session_id, cwd, project) from the +# hook's stdin JSON (passed as $1), then merges any extra jq args you pass. + +# The current protocol version this plugin knows how to produce. +PLUGIN_CURRENT_PROTOCOL_VERSION=1 + +# Negotiate the protocol version with Warp. +# Uses min(plugin_current, warp_declared), falling back to 1 if Warp doesn't advertise a version. +negotiate_protocol_version() { + local warp_version="${WARP_CLI_AGENT_PROTOCOL_VERSION:-1}" + if [ "$warp_version" -lt "$PLUGIN_CURRENT_PROTOCOL_VERSION" ] 2>/dev/null; then + echo "$warp_version" + else + echo "$PLUGIN_CURRENT_PROTOCOL_VERSION" + fi +} + +build_payload() { + local input="$1" + local event="$2" + local agent="auggie" + shift 2 + + local protocol_version + protocol_version=$(negotiate_protocol_version) + + # Extract common fields from the hook input + local session_id cwd project + session_id=$(echo "$input" | jq -r '.conversation_id // empty' 2>/dev/null) + cwd=$(echo "$input" | jq -r '.workspace_roots[0] // empty' 2>/dev/null) + project="" + if [ -n "$cwd" ]; then + project=$(basename "$cwd") + fi + + # Build the payload: common fields + any extra args passed by the caller. + # Extra args should be jq flag pairs like: --arg key "value" or --argjson key '{"a":1}' + jq -nc \ + --argjson v "$protocol_version" \ + --arg agent "$agent" \ + --arg event "$event" \ + --arg session_id "$session_id" \ + --arg cwd "$cwd" \ + --arg project "$project" \ + "$@" \ + '{v:$v, agent:$agent, event:$event, session_id:$session_id, cwd:$cwd, project:$project} + $ARGS.named' +} diff --git a/plugin_marketplace/warp/scripts/legacy/on-notification.sh b/plugin_marketplace/warp/scripts/legacy/on-notification.sh new file mode 100755 index 0000000..f55ea35 --- /dev/null +++ b/plugin_marketplace/warp/scripts/legacy/on-notification.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Hook script for Auggie Notification event +# Sends a Warp notification when Auggie needs user input + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Opt-in debug logging (set AUGGIE_WARP_DEBUG=1) +if [ -n "${AUGGIE_WARP_DEBUG:-}" ]; then + _LOG="${AUGGIE_WARP_DEBUG_LOG:-/tmp/auggie-warp-debug.log}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $(basename "$0") pid=$$ TERM_PROGRAM=${TERM_PROGRAM:-} WARP_PROTO=${WARP_CLI_AGENT_PROTOCOL_VERSION:-} WARP_VER=${WARP_CLIENT_VERSION:-}" >> "$_LOG" +fi + +# Read hook input from stdin +INPUT=$(cat) +[ -n "${AUGGIE_WARP_DEBUG:-}" ] && echo "[stdin] $INPUT" >> "${AUGGIE_WARP_DEBUG_LOG:-/tmp/auggie-warp-debug.log}" + +# Extract the notification message +MSG=$(echo "$INPUT" | jq -r '.message // "Input needed"' 2>/dev/null) +[ -z "$MSG" ] && MSG="Input needed" + +"$SCRIPT_DIR/warp-notify.sh" "Auggie" "$MSG" diff --git a/plugin_marketplace/warp/scripts/legacy/on-session-start.sh b/plugin_marketplace/warp/scripts/legacy/on-session-start.sh new file mode 100755 index 0000000..93739c4 --- /dev/null +++ b/plugin_marketplace/warp/scripts/legacy/on-session-start.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Hook script for Auggie SessionStart event +# Shows welcome message and Warp detection status + +# Opt-in debug logging (set AUGGIE_WARP_DEBUG=1) +if [ -n "${AUGGIE_WARP_DEBUG:-}" ]; then + _LOG="${AUGGIE_WARP_DEBUG_LOG:-/tmp/auggie-warp-debug.log}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $(basename "$0") pid=$$ TERM_PROGRAM=${TERM_PROGRAM:-} WARP_PROTO=${WARP_CLI_AGENT_PROTOCOL_VERSION:-} WARP_VER=${WARP_CLIENT_VERSION:-}" >> "$_LOG" +fi + +# Check if running in Warp terminal +if [ "$TERM_PROGRAM" = "WarpTerminal" ]; then + # Running in Warp - notifications will work + cat << 'EOF' +{ + "systemMessage": "🔔 Warp plugin active. You'll receive native Warp notifications when tasks complete or input is needed." +} +EOF +else + exit 0 +fi diff --git a/plugin_marketplace/warp/scripts/legacy/on-stop.sh b/plugin_marketplace/warp/scripts/legacy/on-stop.sh new file mode 100755 index 0000000..9a0eff2 --- /dev/null +++ b/plugin_marketplace/warp/scripts/legacy/on-stop.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Hook script for Auggie Stop event +# Sends a Warp notification when Auggie completes a task + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Opt-in debug logging (set AUGGIE_WARP_DEBUG=1) +if [ -n "${AUGGIE_WARP_DEBUG:-}" ]; then + _LOG="${AUGGIE_WARP_DEBUG_LOG:-/tmp/auggie-warp-debug.log}" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $(basename "$0") pid=$$ TERM_PROGRAM=${TERM_PROGRAM:-} WARP_PROTO=${WARP_CLI_AGENT_PROTOCOL_VERSION:-} WARP_VER=${WARP_CLIENT_VERSION:-}" >> "$_LOG" +fi + +# Read hook input from stdin +INPUT=$(cat) +[ -n "${AUGGIE_WARP_DEBUG:-}" ] && echo "[stdin] $INPUT" >> "${AUGGIE_WARP_DEBUG_LOG:-/tmp/auggie-warp-debug.log}" + +# Extract transcript path from the hook input +TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null) + +# Default message +MSG="Task completed" + +# Try to extract prompt and response from the transcript (JSONL format) +if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then + # Get the first user prompt + PROMPT=$(jq -rs ' + [.[] | select(.type == "user")] | first | .message.content // empty + ' "$TRANSCRIPT_PATH" 2>/dev/null) + + # Get the last assistant response + RESPONSE=$(jq -rs ' + [.[] | select(.type == "assistant" and .message.content)] | last | + [.message.content[] | select(.type == "text") | .text] | join(" ") + ' "$TRANSCRIPT_PATH" 2>/dev/null) + + if [ -n "$PROMPT" ] && [ -n "$RESPONSE" ]; then + # Truncate prompt to 50 chars + if [ ${#PROMPT} -gt 50 ]; then + PROMPT="${PROMPT:0:47}..." + fi + # Truncate response to 120 chars + if [ ${#RESPONSE} -gt 120 ]; then + RESPONSE="${RESPONSE:0:117}..." + fi + MSG="\"${PROMPT}\" → ${RESPONSE}" + elif [ -n "$RESPONSE" ]; then + # Fallback to just response if no prompt found + if [ ${#RESPONSE} -gt 175 ]; then + RESPONSE="${RESPONSE:0:172}..." + fi + MSG="$RESPONSE" + fi +fi + +"$SCRIPT_DIR/warp-notify.sh" "Auggie" "$MSG" diff --git a/plugin_marketplace/warp/scripts/legacy/warp-notify.sh b/plugin_marketplace/warp/scripts/legacy/warp-notify.sh new file mode 100755 index 0000000..6ca0588 --- /dev/null +++ b/plugin_marketplace/warp/scripts/legacy/warp-notify.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Warp notification utility using OSC escape sequences +# Usage: warp-notify.sh <body> + +TITLE="${1:-Notification}" +BODY="${2:-}" + +# OSC 777 format: \033]777;notify;<title>;<body>\007 +# Write directly to /dev/tty to ensure it reaches the terminal +printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true diff --git a/plugin_marketplace/warp/scripts/on-notification.sh b/plugin_marketplace/warp/scripts/on-notification.sh new file mode 100755 index 0000000..b330ce7 --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-notification.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Hook script for Auggie Notification event (idle_prompt only) +# Sends a structured Warp notification when Auggie has been idle + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# Legacy fallback for old Warp versions +if ! should_use_structured; then + [ "$TERM_PROGRAM" = "WarpTerminal" ] && exec "$SCRIPT_DIR/legacy/on-notification.sh" + exit 0 +fi + +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +# Extract notification-specific fields +NOTIF_TYPE=$(echo "$INPUT" | jq -r '.notification_type // "unknown"' 2>/dev/null) +MSG=$(echo "$INPUT" | jq -r '.notification_message // "Input needed"' 2>/dev/null) +[ -z "$MSG" ] && MSG="Input needed" + +BODY=$(build_payload "$INPUT" "$NOTIF_TYPE" \ + --arg summary "$MSG") +"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugin_marketplace/warp/scripts/on-post-tool-use.sh b/plugin_marketplace/warp/scripts/on-post-tool-use.sh new file mode 100755 index 0000000..a177a2b --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-post-tool-use.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Hook script for Auggie PostToolUse event +# Sends a structured Warp notification after a tool call completes, +# transitioning the session status from Blocked back to Running. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# No legacy equivalent for this hook +if ! should_use_structured; then + exit 0 +fi + +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) + +BODY=$(build_payload "$INPUT" "tool_complete" \ + --arg tool_name "$TOOL_NAME") + +"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugin_marketplace/warp/scripts/on-pre-tool-use.sh b/plugin_marketplace/warp/scripts/on-pre-tool-use.sh new file mode 100755 index 0000000..6cedbba --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-pre-tool-use.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Hook script for Auggie PreToolUse event +# Detects when Auggie is waiting for user input (ask-user tool) and sends +# a "permission_request" so Warp shows the stop sign / blocked indicator. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# No legacy equivalent for this hook +if ! should_use_structured; then + exit 0 +fi + +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) + +# ask-user means the agent is waiting for input (plan mode / permission). +# Send "permission_request" so Warp shows the stop sign instead of "in progress". +if [ "$TOOL_NAME" = "ask-user" ]; then + BODY=$(build_payload "$INPUT" "permission_request" \ + --arg tool_name "$TOOL_NAME") + "$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" +fi diff --git a/plugin_marketplace/warp/scripts/on-prompt-submit.sh b/plugin_marketplace/warp/scripts/on-prompt-submit.sh new file mode 100755 index 0000000..038ef54 --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-prompt-submit.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Hook script for Auggie PromptSubmit event +# Sends a "prompt_submit" Warp notification when the user submits a prompt, +# transitioning the session status from idle/done back to running. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# No legacy equivalent for this hook +if ! should_use_structured; then + exit 0 +fi + +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +# Extract the user's prompt +QUERY=$(echo "$INPUT" | jq -r '.user_prompt // empty' 2>/dev/null) +if [ -n "$QUERY" ] && [ ${#QUERY} -gt 200 ]; then + QUERY="${QUERY:0:197}..." +fi + +BODY=$(build_payload "$INPUT" "prompt_submit" \ + --arg query "$QUERY") +"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugin_marketplace/warp/scripts/on-session-start.sh b/plugin_marketplace/warp/scripts/on-session-start.sh new file mode 100755 index 0000000..eb70f12 --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-session-start.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Hook script for Auggie SessionStart event +# Shows welcome message, Warp detection status, and emits plugin version + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# Legacy fallback for old Warp versions +if ! should_use_structured; then + exec "$SCRIPT_DIR/legacy/on-session-start.sh" +fi + +if ! command -v jq &>/dev/null; then + cat << 'EOF' +{ + "systemMessage": "🚨 Warp notifications require jq! Install it with your system package manager (e.g. brew install jq, apt install jq) 🚨" +} +EOF + exit 0 +fi +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +# Read plugin version from plugin.json +PLUGIN_VERSION=$(jq -r '.version // "unknown"' "$SCRIPT_DIR/../.augment-plugin/plugin.json" 2>/dev/null) + +# Emit structured notification with plugin version so Warp can track it +BODY=$(build_payload "$INPUT" "session_start" \ + --arg plugin_version "$PLUGIN_VERSION") +"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugin_marketplace/warp/scripts/on-stop.sh b/plugin_marketplace/warp/scripts/on-stop.sh new file mode 100755 index 0000000..b6f0c1d --- /dev/null +++ b/plugin_marketplace/warp/scripts/on-stop.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Hook script for Auggie Stop event +# Sends a structured Warp notification when Auggie completes a task + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/should-use-structured.sh" + +# Legacy fallback for old Warp versions +if ! should_use_structured; then + [ "$TERM_PROGRAM" = "WarpTerminal" ] && exec "$SCRIPT_DIR/legacy/on-stop.sh" + exit 0 +fi + +source "$SCRIPT_DIR/build-payload.sh" + +# Read hook input from stdin +INPUT=$(cat) + +QUERY=$(echo "$INPUT" | jq -r '.conversation.userPrompt // ""' 2>/dev/null) +RESPONSE=$(echo "$INPUT" | jq -r '.conversation.agentTextResponse // ""' 2>/dev/null) + +# Truncate for notification display +if [ -n "$QUERY" ] && [ ${#QUERY} -gt 200 ]; then + QUERY="${QUERY:0:197}..." +fi +if [ -n "$RESPONSE" ] && [ ${#RESPONSE} -gt 200 ]; then + RESPONSE="${RESPONSE:0:197}..." +fi + +BODY=$(build_payload "$INPUT" "stop" \ + --arg query "$QUERY" \ + --arg response "$RESPONSE") +"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugin_marketplace/warp/scripts/should-use-structured.sh b/plugin_marketplace/warp/scripts/should-use-structured.sh new file mode 100755 index 0000000..13360e0 --- /dev/null +++ b/plugin_marketplace/warp/scripts/should-use-structured.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Determines whether the current Warp build supports structured CLI agent notifications. +# +# Usage: +# source "$SCRIPT_DIR/should-use-structured.sh" +# if should_use_structured; then +# # ... send structured notification +# else +# # ... legacy fallback or exit +# fi +# +# Returns 0 (true) when structured notifications are safe to use, 1 (false) otherwise. + +# Last known Warp release per channel that unconditionally set +# WARP_CLI_AGENT_PROTOCOL_VERSION without gating it behind the +# HOANotifications feature flag. These builds advertise protocol +# support but can't actually render structured notifications. +LAST_BROKEN_DEV="" +LAST_BROKEN_STABLE="v0.2026.03.25.08.24.stable_05" +LAST_BROKEN_PREVIEW="v0.2026.03.25.08.24.preview_05" + +should_use_structured() { + # No protocol version advertised → Warp doesn't know about structured notifications. + [ -z "${WARP_CLI_AGENT_PROTOCOL_VERSION:-}" ] && return 1 + + # No client version available → can't verify this build has the fix. + # (This catches the broken prod release before this was set, but after WARP_CLI_AGENT_PROTOCOL_VERSION was set without a flag check.) + [ -z "${WARP_CLIENT_VERSION:-}" ] && return 1 + + # Check whether this version is at or before the last broken release for its channel. + local threshold="" + case "$WARP_CLIENT_VERSION" in + *dev*) threshold="$LAST_BROKEN_DEV" ;; + *stable*) threshold="$LAST_BROKEN_STABLE" ;; + *preview*) threshold="$LAST_BROKEN_PREVIEW" ;; + esac + + # If we matched a channel and the version is <= the broken threshold, fall back. + if [ -n "$threshold" ] && [[ ! "$WARP_CLIENT_VERSION" > "$threshold" ]]; then + return 1 + fi + + return 0 +} diff --git a/plugin_marketplace/warp/scripts/warp-notify.sh b/plugin_marketplace/warp/scripts/warp-notify.sh new file mode 100755 index 0000000..523f873 --- /dev/null +++ b/plugin_marketplace/warp/scripts/warp-notify.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Warp notification utility using OSC escape sequences +# Usage: warp-notify.sh <title> <body> +# +# For structured Warp notifications, title should be "warp://cli-agent" +# and body should be a JSON string matching the cli-agent notification schema. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/should-use-structured.sh" + +# Only emit notifications when we've confirmed the Warp build can render them. +if ! should_use_structured; then + exit 0 +fi + +TITLE="${1:-Notification}" +BODY="${2:-}" + +# OSC 777 format: \033]777;notify;<title>;<body>\007 +# Write directly to /dev/tty to ensure it reaches the terminal +printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true diff --git a/plugin_marketplace/warp/tests/test-hooks.sh b/plugin_marketplace/warp/tests/test-hooks.sh new file mode 100755 index 0000000..ebc92b5 --- /dev/null +++ b/plugin_marketplace/warp/tests/test-hooks.sh @@ -0,0 +1,233 @@ +#!/bin/bash +# Tests for the Warp Auggie plugin hook scripts. +# +# Validates that each hook script produces correctly structured JSON payloads +# by piping mock Auggie hook input into the scripts and checking the output. +# +# Usage: ./tests/test-hooks.sh +# +# Since the hook scripts write OSC sequences to /dev/tty (not stdout), +# we test build-payload.sh directly — it's the shared JSON construction logic +# that all hook scripts use. + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" +source "$SCRIPT_DIR/build-payload.sh" + +PASSED=0 +FAILED=0 + +# --- Test helpers --- + +assert_eq() { + local test_name="$1" + local expected="$2" + local actual="$3" + if [ "$expected" = "$actual" ]; then + echo " ✓ $test_name" + PASSED=$((PASSED + 1)) + else + echo " ✗ $test_name" + echo " expected: $expected" + echo " actual: $actual" + FAILED=$((FAILED + 1)) + fi +} + +assert_json_field() { + local test_name="$1" + local json="$2" + local field="$3" + local expected="$4" + local actual + actual=$(echo "$json" | jq -r "$field" 2>/dev/null) + assert_eq "$test_name" "$expected" "$actual" +} + +# --- Tests --- + +echo "=== build-payload.sh ===" + +echo "" +echo "--- Common fields ---" +PAYLOAD=$(build_payload '{"conversation_id":"sess-123","workspace_roots":["/Users/alice/my-project"]}' "stop") +assert_json_field "v is 1" "$PAYLOAD" ".v" "1" +assert_json_field "agent is auggie" "$PAYLOAD" ".agent" "auggie" +assert_json_field "event is stop" "$PAYLOAD" ".event" "stop" +assert_json_field "session_id extracted" "$PAYLOAD" ".session_id" "sess-123" +assert_json_field "cwd extracted" "$PAYLOAD" ".cwd" "/Users/alice/my-project" +assert_json_field "project is basename of cwd" "$PAYLOAD" ".project" "my-project" + +echo "" +echo "--- Common fields with missing data ---" +PAYLOAD=$(build_payload '{}' "stop") +assert_json_field "empty session_id" "$PAYLOAD" ".session_id" "" +assert_json_field "empty cwd" "$PAYLOAD" ".cwd" "" +assert_json_field "empty project" "$PAYLOAD" ".project" "" + +echo "" +echo "--- Extra args are merged ---" +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp/proj"]}' "stop" \ + --arg query "hello" \ + --arg response "world") +assert_json_field "query merged" "$PAYLOAD" ".query" "hello" +assert_json_field "response merged" "$PAYLOAD" ".response" "world" +assert_json_field "common fields still present" "$PAYLOAD" ".session_id" "s1" + +echo "" +echo "--- Stop event ---" +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp/proj"]}' "stop" \ + --arg query "write a haiku" \ + --arg response "Memory is safe, the borrow checker stands guard" \ + --arg transcript_path "/tmp/transcript.jsonl") +assert_json_field "event is stop" "$PAYLOAD" ".event" "stop" +assert_json_field "query present" "$PAYLOAD" ".query" "write a haiku" +assert_json_field "response present" "$PAYLOAD" ".response" "Memory is safe, the borrow checker stands guard" +assert_json_field "transcript_path present" "$PAYLOAD" ".transcript_path" "/tmp/transcript.jsonl" + +echo "" +echo "--- Permission request event ---" +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp/proj"]}' "permission_request" \ + --arg summary "Wants to run Bash: rm -rf /tmp" \ + --arg tool_name "Bash" \ + --argjson tool_input '{"command":"rm -rf /tmp"}') +assert_json_field "event is permission_request" "$PAYLOAD" ".event" "permission_request" +assert_json_field "summary present" "$PAYLOAD" ".summary" "Wants to run Bash: rm -rf /tmp" +assert_json_field "tool_name present" "$PAYLOAD" ".tool_name" "Bash" +assert_json_field "tool_input.command present" "$PAYLOAD" ".tool_input.command" "rm -rf /tmp" + +echo "" +echo "--- Idle prompt event ---" +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp/proj"],"notification_type":"idle_prompt"}' "idle_prompt" \ + --arg summary "Auggie is waiting for your input") +assert_json_field "event is idle_prompt" "$PAYLOAD" ".event" "idle_prompt" +assert_json_field "summary present" "$PAYLOAD" ".summary" "Auggie is waiting for your input" + +echo "" +echo "--- JSON special characters in values ---" +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp/proj"]}' "stop" \ + --arg query 'what does "hello world" mean?' \ + --arg response 'It means greeting. Use: printf("hello")') +assert_json_field "quotes in query preserved" "$PAYLOAD" ".query" 'what does "hello world" mean?' +assert_json_field "parens in response preserved" "$PAYLOAD" ".response" 'It means greeting. Use: printf("hello")' + +echo "" +echo "--- Protocol version negotiation ---" + +# Default: no env var set → falls back to plugin max (1) +unset WARP_CLI_AGENT_PROTOCOL_VERSION +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp"]}' "stop") +assert_json_field "defaults to v1 when env var absent" "$PAYLOAD" ".v" "1" + +# Warp declares v1 → use 1 +export WARP_CLI_AGENT_PROTOCOL_VERSION=1 +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp"]}' "stop") +assert_json_field "v1 when warp declares 1" "$PAYLOAD" ".v" "1" + +# Warp declares a higher version than the plugin knows → capped to plugin current +export WARP_CLI_AGENT_PROTOCOL_VERSION=99 +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp"]}' "stop") +assert_json_field "capped to plugin current when warp is ahead" "$PAYLOAD" ".v" "1" + +# Warp declares a lower version than the plugin knows → use warp's version +# (not testable with PLUGIN_MAX=1 since there's no v0, but we verify the min logic +# by temporarily overriding the variable) +PLUGIN_CURRENT_PROTOCOL_VERSION=5 +export WARP_CLI_AGENT_PROTOCOL_VERSION=3 +PAYLOAD=$(build_payload '{"conversation_id":"s1","workspace_roots":["/tmp"]}' "stop") +assert_json_field "uses warp version when plugin is ahead" "$PAYLOAD" ".v" "3" +PLUGIN_CURRENT_PROTOCOL_VERSION=1 + +# Clean up +unset WARP_CLI_AGENT_PROTOCOL_VERSION + +echo "" +echo "=== should-use-structured.sh ===" + +source "$SCRIPT_DIR/../scripts/should-use-structured.sh" + +echo "" +echo "--- No protocol version → legacy ---" +unset WARP_CLI_AGENT_PROTOCOL_VERSION +unset WARP_CLIENT_VERSION +should_use_structured +assert_eq "no protocol version returns false" "1" "$?" + +echo "" +echo "--- Protocol set, no client version → legacy ---" +export WARP_CLI_AGENT_PROTOCOL_VERSION=1 +unset WARP_CLIENT_VERSION +should_use_structured +assert_eq "missing WARP_CLIENT_VERSION returns false" "1" "$?" + +echo "" +echo "--- Protocol set, dev version → always structured (dev was never broken) ---" +export WARP_CLI_AGENT_PROTOCOL_VERSION=1 +export WARP_CLIENT_VERSION="v0.2026.03.30.08.43.dev_00" +should_use_structured +assert_eq "dev version returns true" "0" "$?" + +echo "" +echo "--- Protocol set, broken stable version → legacy ---" +export WARP_CLIENT_VERSION="v0.2026.03.25.08.24.stable_05" +should_use_structured +assert_eq "exact broken stable version returns false" "1" "$?" + +echo "" +echo "--- Protocol set, newer stable version → structured ---" +export WARP_CLIENT_VERSION="v0.2026.04.01.08.00.stable_00" +should_use_structured +assert_eq "newer stable version returns true" "0" "$?" + +echo "" +echo "--- Protocol set, broken preview version → legacy ---" +export WARP_CLIENT_VERSION="v0.2026.03.25.08.24.preview_05" +should_use_structured +assert_eq "exact broken preview version returns false" "1" "$?" + +echo "" +echo "--- Protocol set, newer preview version → structured ---" +export WARP_CLIENT_VERSION="v0.2026.04.01.08.00.preview_00" +should_use_structured +assert_eq "newer preview version returns true" "0" "$?" + +# Clean up +unset WARP_CLI_AGENT_PROTOCOL_VERSION +unset WARP_CLIENT_VERSION + +# --- Routing tests --- +# These test the hook scripts as subprocesses to verify routing behavior. +# We override /dev/tty writes since they'd fail in CI. + +HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../scripts" && pwd)" + +echo "" +echo "=== Routing ===" + +echo "" +echo "--- SessionStart routing ---" + +# Legacy Warp (TERM_PROGRAM=WarpTerminal, no protocol version) +OUTPUT=$(TERM_PROGRAM=WarpTerminal bash "$HOOK_DIR/on-session-start.sh" < /dev/null 2>/dev/null) +SYS_MSG=$(echo "$OUTPUT" | jq -r '.systemMessage // empty' 2>/dev/null) +assert_eq "legacy Warp shows active message" \ + "🔔 Warp plugin active. You'll receive native Warp notifications when tasks complete or input is needed." \ + "$SYS_MSG" + +echo "" +echo "--- Modern-only hooks exit silently without protocol version ---" + +for HOOK in on-post-tool-use.sh; do + echo '{}' | bash "$HOOK_DIR/$HOOK" 2>/dev/null + assert_eq "$HOOK exits 0 without protocol version" "0" "$?" +done + +# --- Summary --- + +echo "" +echo "=== Results: $PASSED passed, $FAILED failed ===" + +if [ "$FAILED" -gt 0 ]; then + exit 1 +fi