aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/typing-indicators.md80
-rw-r--r--internal/handler/websocket/handler.go15
-rwxr-xr-xmuxstrbin0 -> 27788215 bytes
3 files changed, 95 insertions, 0 deletions
diff --git a/docs/typing-indicators.md b/docs/typing-indicators.md
new file mode 100644
index 0000000..a85633d
--- /dev/null
+++ b/docs/typing-indicators.md
@@ -0,0 +1,80 @@
1# Typing/Thinking Indicators
2
3Ephemeral events for real-time activity feedback in Nostr conversations.
4
5## Event Kind
6
7**Kind 20001** - Typing/Thinking Indicator (ephemeral)
8
9Per NIP-01, kinds 20000-29999 are ephemeral and should not be stored by relays.
10
11## Event Structure
12
13```json
14{
15 "kind": 20001,
16 "content": "<status>",
17 "tags": [
18 ["p", "<recipient_pubkey>"],
19 ["e", "<event_id_being_replied_to>", "", "reply"]
20 ],
21 "created_at": <unix_timestamp>,
22 "pubkey": "<sender_pubkey>",
23 "id": "<event_id>",
24 "sig": "<signature>"
25}
26```
27
28### Content Values
29
30| Value | Meaning |
31|-------|---------|
32| `typing` | User is composing a message |
33| `thinking` | Agent is processing/generating |
34| `stopped` | Activity stopped (explicit clear) |
35| `` (empty) | Clear indicator |
36
37### Tags
38
39- `p` (required): Pubkey of the recipient
40- `e` (optional): Event being replied to, with `reply` marker
41
42## Client Behavior
43
44### Sending
451. Publish `thinking` when starting to process a message
462. Publish actual response when done
473. Optionally publish `stopped` or empty content to clear early
48
49### Receiving
501. Subscribe to kind 20001 events where you're tagged
512. Display indicator while active
523. Auto-clear after ~15 seconds if no update (stale indicator)
534. Clear immediately when:
54 - Receive `stopped` or empty content from same pubkey
55 - Receive an actual message (kind 1, 4, 14, etc.) from same pubkey
56
57## Example Flow
58
59```
60Agent receives message
61 └─> Publish kind:20001 content:"thinking" p:[user]
62 └─> Process request...
63 └─> Publish kind:1 content:"Here's the answer..." (reply)
64 └─> (indicator auto-clears on client when they see the kind:1)
65```
66
67## Relay Support
68
69Relays SHOULD:
70- Forward ephemeral events to matching subscriptions
71- NOT persist ephemeral events to storage
72- NOT return ephemeral events in REQ responses for historical data
73
74muxstr handles this correctly as of the ephemeral event support addition.
75
76## Security Considerations
77
78- Indicators reveal that someone is actively engaged with you
79- Consider privacy implications before implementing
80- Clients MAY offer a setting to disable sending indicators
diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go
index c64f3f9..b591f61 100644
--- a/internal/handler/websocket/handler.go
+++ b/internal/handler/websocket/handler.go
@@ -359,6 +359,14 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j
359 return nil 359 return nil
360 } 360 }
361 361
362 // Handle ephemeral events (kinds 20000-29999) - broadcast but don't store
363 if isEphemeralKind(pbEvent.Kind) {
364 h.subs.MatchAndFan(pbEvent)
365 status = "ok"
366 h.sendOK(ctx, conn, event.ID, true, "")
367 return nil
368 }
369
362 eventData := &storage.EventData{ 370 eventData := &storage.EventData{
363 Event: pbEvent, 371 Event: pbEvent,
364 CanonicalJSON: canonicalJSON, 372 CanonicalJSON: canonicalJSON,
@@ -643,3 +651,10 @@ func (h *Handler) isAllowedKind(kind int32) bool {
643 } 651 }
644 return h.allowedKinds[kind] 652 return h.allowedKinds[kind]
645} 653}
654
655// isEphemeralKind returns true if the event kind is ephemeral (20000-29999).
656// Ephemeral events are broadcast to subscribers but not stored.
657// See NIP-01: https://github.com/nostr-protocol/nips/blob/master/01.md
658func isEphemeralKind(kind int32) bool {
659 return kind >= 20000 && kind < 30000
660}
diff --git a/muxstr b/muxstr
new file mode 100755
index 0000000..6d32892
--- /dev/null
+++ b/muxstr
Binary files differ