diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 2e2bcc2..ffd8069 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "name": "warp", "description": "Native Warp notifications when Claude completes tasks or needs input", "source": "./plugins/warp", - "version": "2.3.0", + "version": "2.4.0", "category": "productivity", "tags": ["notifications", "terminal", "warp"] }, diff --git a/README.md b/README.md index 9bd0c27..fa0930a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Payloads include a protocol version negotiated between the plugin and Warp (`min The plugin registers lifecycle hooks including: - **SessionStart** — emits the plugin version and a welcome system message -- **Stop** — reads the transcript to extract your prompt and Claude's response, then sends a task-complete notification +- **Stop** — reads the transcript to extract your prompt and Claude's response, then sends a task-complete notification that also reports how many background tasks and scheduled wakeups are still pending, so Warp can tell a finished session from one waiting on background work - **Notification** (`idle_prompt`, `agent_needs_input`) — fires when Claude is waiting for user input - **PermissionRequest** — fires when Claude wants to run a tool, includes the tool name and a preview of its input - **UserPromptSubmit** — fires when you submit a prompt, signaling the session is active again diff --git a/plugins/warp/.claude-plugin/plugin.json b/plugins/warp/.claude-plugin/plugin.json index 219e43a..dd8f2ba 100644 --- a/plugins/warp/.claude-plugin/plugin.json +++ b/plugins/warp/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "warp", "description": "Warp terminal integration for Claude Code - native notifications, and more to come", - "version": "2.3.0", + "version": "2.4.0", "author": { "name": "Warp", "url": "https://warp.dev" diff --git a/plugins/warp/scripts/on-stop.sh b/plugins/warp/scripts/on-stop.sh index 4163bb9..34e06f7 100755 --- a/plugins/warp/scripts/on-stop.sh +++ b/plugins/warp/scripts/on-stop.sh @@ -65,9 +65,17 @@ if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then fi fi +# Claude Code wakes the agent again when a background task finishes or a session cron fires, +# so a Stop with either pending is a pause rather than the end of the session. Default to 0 +# so --argjson always receives a number and the notification is never dropped. +BACKGROUND_TASK_COUNT=$(echo "$INPUT" | jq -r '.background_tasks // [] | length' 2>/dev/null) +SESSION_CRON_COUNT=$(echo "$INPUT" | jq -r '.session_crons // [] | length' 2>/dev/null) + BODY=$(build_payload "$INPUT" "stop" \ --arg query "$QUERY" \ --arg response "$RESPONSE" \ - --arg transcript_path "$TRANSCRIPT_PATH") + --arg transcript_path "$TRANSCRIPT_PATH" \ + --argjson background_task_count "${BACKGROUND_TASK_COUNT:-0}" \ + --argjson session_cron_count "${SESSION_CRON_COUNT:-0}") "$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY" diff --git a/plugins/warp/tests/test-hooks.sh b/plugins/warp/tests/test-hooks.sh index 2dc273a..e2d3ea7 100755 --- a/plugins/warp/tests/test-hooks.sh +++ b/plugins/warp/tests/test-hooks.sh @@ -56,7 +56,7 @@ MARKETPLACE_VERSION=$(jq -r '.plugins[] | select(.name == "warp") | .version' \ "$REPO_DIR/.claude-plugin/marketplace.json") NOTIFICATION_MATCHER=$(jq -r '.hooks.Notification[0].matcher' "$PLUGIN_DIR/hooks/hooks.json") -assert_eq "plugin version bumped" "2.3.0" "$PLUGIN_VERSION" +assert_eq "plugin version bumped" "2.4.0" "$PLUGIN_VERSION" assert_eq "marketplace version matches plugin" "$PLUGIN_VERSION" "$MARKETPLACE_VERSION" assert_eq "notification matcher includes input-required events" \ "idle_prompt|agent_needs_input" \ @@ -102,6 +102,17 @@ 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 "--- Stop event pending background work ---" +STOP_INPUT='{"session_id":"s1","cwd":"/tmp/proj","background_tasks":[{"id":"t1","type":"shell","status":"running"},{"id":"t2","type":"subagent","status":"running"}],"session_crons":[{"id":"c1","schedule":"* * * * *"}]}' +PAYLOAD=$(build_payload "$STOP_INPUT" "stop" \ + --argjson background_task_count "$(echo "$STOP_INPUT" | jq -r '.background_tasks // [] | length')" \ + --argjson session_cron_count "$(echo "$STOP_INPUT" | jq -r '.session_crons // [] | length')") +assert_json_field "background_task_count is numeric" "$PAYLOAD" ".background_task_count | type" "number" +assert_json_field "background_task_count merged" "$PAYLOAD" ".background_task_count" "2" +assert_json_field "session_cron_count merged" "$PAYLOAD" ".session_cron_count" "1" +assert_eq "missing arrays count as 0" "0" "$(echo '{"session_id":"s1"}' | jq -r '.background_tasks // [] | length')" + echo "" echo "--- Permission request event ---" PAYLOAD=$(build_payload '{"session_id":"s1","cwd":"/tmp/proj"}' "permission_request" \