Sweeping AGENTS.md updates -- endpoints, feature priorities, etc.

This commit is contained in:
Jack Kingsman
2026-02-09 19:24:45 -08:00
parent 18bdc0f83b
commit 333d885bdb
3 changed files with 61 additions and 19 deletions

View File

@@ -61,6 +61,30 @@ A web interface for MeshCore mesh radio networks. The backend connects to a Mesh
└─────────────┘
```
## Feature Priority
**Primary (must work correctly):**
- Sending and receiving direct messages and channel messages
- Accurate message display: correct ordering, deduplication, pagination/history loading, and real-time updates without data loss or duplicates
- Accurate ACK tracking, repeat/echo counting, and path display
- Historical packet decryption (recovering incoming messages using newly-added keys)
- Outgoing DMs are stored as plaintext by the send endpoint — no decryption needed
**Secondary:**
- Channel key cracker (WebGPU brute-force)
- Repeater management (telemetry, CLI commands, ACL)
**Tertiary (best-effort, quality-of-life):**
- Raw packet feed — a debug/observation tool ("radio aquarium"); interesting to watch or copy packets from, but not critical infrastructure
- Map view — visual display of node locations from advertisements
- Network visualizer — force-directed graph of mesh topology
- Bot system — automated message responses
- Read state tracking / mark-all-read — convenience feature for unread badges; no need for transactional atomicity or race-condition hardening
## Error Handling Philosophy
**Background tasks** (WebSocket broadcasts, periodic sync, contact auto-loading, etc.) use fire-and-forget `asyncio.create_task`. Exceptions in these tasks are logged to the backend logs, which is sufficient for debugging. There is no need to track task references or add done-callbacks purely for error visibility. If there's a convenient way to bubble an error to the frontend (e.g., via `broadcast_error` for user-actionable problems), do so, but this is minor and best-effort.
## Key Design Principles
1. **Store-and-serve**: Backend stores all packets even when no client is connected
@@ -99,7 +123,7 @@ The following are **deliberate design choices**, not bugs. They are documented i
**Direct messages**: Expected ACK code is tracked. When ACK event arrives, message marked as acked.
**Channel messages**: Flood messages echo back. The decoder identifies repeats by matching (channel_idx, text_hash, timestamp ±5s) and marks the original as "acked".
**Channel messages**: Flood messages echo back through repeaters. Repeats are identified by the database UNIQUE constraint on `(type, conversation_key, text, sender_timestamp)` — when an INSERT hits a duplicate, `_handle_duplicate_message()` in `packet_processor.py` increments the ack count on the original and adds the new path. There is no timestamp-windowed matching; deduplication is exact-match only.
## Directory Structure
@@ -217,29 +241,39 @@ All endpoints are prefixed with `/api` (e.g., `/api/health`).
| GET | `/api/health` | Connection status |
| GET | `/api/radio/config` | Radio configuration |
| PATCH | `/api/radio/config` | Update name, location, radio params |
| POST | `/api/radio/advertise` | Send advertisement |
| POST | `/api/radio/reconnect` | Manual radio reconnection |
| POST | `/api/radio/reboot` | Reboot radio or reconnect if disconnected |
| PUT | `/api/radio/private-key` | Import private key to radio |
| POST | `/api/radio/advertise` | Send advertisement |
| POST | `/api/radio/reboot` | Reboot radio or reconnect if disconnected |
| POST | `/api/radio/reconnect` | Manual radio reconnection |
| GET | `/api/contacts` | List contacts |
| GET | `/api/contacts/{key}` | Get contact by public key or prefix |
| POST | `/api/contacts` | Create contact (optionally trigger historical DM decrypt) |
| DELETE | `/api/contacts/{key}` | Delete contact |
| POST | `/api/contacts/sync` | Pull from radio |
| POST | `/api/contacts/{key}/add-to-radio` | Push contact to radio |
| POST | `/api/contacts/{key}/remove-from-radio` | Remove contact from radio |
| POST | `/api/contacts/{key}/mark-read` | Mark contact conversation as read |
| POST | `/api/contacts/{key}/telemetry` | Request telemetry from repeater |
| POST | `/api/contacts/{key}/command` | Send CLI command to repeater |
| POST | `/api/contacts/{key}/trace` | Trace route to contact |
| GET | `/api/channels` | List channels |
| GET | `/api/channels/{key}` | Get channel by key |
| POST | `/api/channels` | Create channel |
| DELETE | `/api/channels/{key}` | Delete channel |
| POST | `/api/channels/sync` | Pull from radio |
| POST | `/api/channels/{key}/mark-read` | Mark channel as read |
| GET | `/api/messages` | List with filters |
| POST | `/api/messages/direct` | Send direct message |
| POST | `/api/messages/channel` | Send channel message |
| GET | `/api/packets/undecrypted/count` | Count of undecrypted packets |
| POST | `/api/packets/decrypt/historical` | Decrypt stored packets |
| GET | `/api/packets/decrypt/progress` | Get historical decryption progress |
| POST | `/api/packets/maintenance` | Delete old packets (cleanup) |
| POST | `/api/contacts/{key}/mark-read` | Mark contact conversation as read |
| POST | `/api/channels/{key}/mark-read` | Mark channel as read |
| POST | `/api/packets/maintenance` | Delete old packets and vacuum |
| GET | `/api/read-state/unreads` | Server-computed unread counts, mentions, last message times |
| POST | `/api/read-state/mark-all-read` | Mark all conversations as read |
| GET | `/api/settings` | Get app settings |
| PATCH | `/api/settings` | Update app settings |
| POST | `/api/settings/favorites/toggle` | Toggle favorite status |
| POST | `/api/settings/migrate` | One-time migration from frontend localStorage |
| WS | `/api/ws` | Real-time updates |
## Key Concepts
@@ -321,6 +355,7 @@ mc.subscribe(EventType.ACK, handler)
| `MESHCORE_BLE_ADDRESS` | *(none)* | BLE device address (mutually exclusive with serial/TCP) |
| `MESHCORE_BLE_PIN` | *(required with BLE)* | BLE PIN code |
| `MESHCORE_DATABASE_PATH` | `data/meshcore.db` | SQLite database location |
| `MESHCORE_MAX_RADIO_CONTACTS` | `200` | Max recent contacts to keep on radio for DM ACKs |
**Note:** `max_radio_contacts` is a runtime setting stored in the database (`app_settings` table), not an environment variable. It is configured via `PATCH /api/settings`.
**Transport mutual exclusivity:** Only one of `MESHCORE_SERIAL_PORT`, `MESHCORE_TCP_HOST`, or `MESHCORE_BLE_ADDRESS` may be set. If none are set, serial auto-detection is used.

View File

@@ -232,7 +232,7 @@ messages (
text TEXT NOT NULL,
sender_timestamp INTEGER,
received_at INTEGER NOT NULL,
path TEXT, -- Hex-encoded routing path (2 chars per hop), null for outgoing
paths TEXT, -- JSON array of {path, received_at} for multiple delivery paths
txt_type INTEGER DEFAULT 0,
signature TEXT,
outgoing INTEGER DEFAULT 0,
@@ -244,10 +244,8 @@ raw_packets (
id INTEGER PRIMARY KEY,
timestamp INTEGER NOT NULL,
data BLOB NOT NULL, -- Raw packet bytes
decrypted INTEGER DEFAULT 0,
message_id INTEGER, -- FK to messages if decrypted
decrypt_attempts INTEGER DEFAULT 0,
last_attempt INTEGER,
payload_hash TEXT, -- SHA256 of payload for deduplication (UNIQUE index)
FOREIGN KEY (message_id) REFERENCES messages(id)
)
@@ -364,8 +362,16 @@ packet, `packet_processor.py` handles the complete flow:
export, unknown contact), `on_contact_message` handles DMs from the MeshCore library's
`CONTACT_MSG_RECV` event. DB deduplication prevents double-storage when both paths fire.
**Outgoing DMs**: Outgoing direct messages are only sent via the app's REST API
(`POST /api/messages/direct`), which stores the plaintext directly in the database.
No decryption is needed for outgoing DMs. The real-time packet processor may also see
the outgoing packet via `RX_LOG_DATA`, but the DB UNIQUE constraint deduplicates it
against the already-stored plaintext. Historical decryption intentionally skips outgoing
packets for the same reason — the app already has the plaintext.
**Historical decryption**: When creating a contact with `try_historical=True`, the server
attempts to decrypt all stored `TEXT_MESSAGE` packets for that contact.
attempts to decrypt all stored `TEXT_MESSAGE` packets for that contact. This only recovers
**incoming** messages; outgoing DMs are already stored as plaintext by the send endpoint.
**Direction detection**: The decoder uses the 1-byte dest_hash and src_hash to determine
if a message is incoming or outgoing. Edge case: when both bytes match (1/256 chance),
@@ -443,10 +449,11 @@ When ACK event arrives, the message's ack count is incremented.
### Channel Message Repeats
Flood messages echo back through repeaters. Detection uses:
- Channel key
- Text hash
- Timestamp (±5 second window)
Flood messages echo back through repeaters. Repeats are detected via the database
UNIQUE constraint on `(type, conversation_key, text, sender_timestamp)`. When an INSERT
hits a duplicate, `_handle_duplicate_message()` in `packet_processor.py` increments the
ack count and adds the new delivery path. There is no timestamp-windowed matching;
deduplication is exact-match only.
Each repeat increments the ack count. The frontend displays:
- `?` = no acks

View File

@@ -47,7 +47,7 @@ frontend/
│ │ ├── MessageList.tsx # Message display, avatars, clickable senders
│ │ ├── MessageInput.tsx # Text input with imperative handle
│ │ ├── ContactAvatar.tsx # Contact profile image component
│ │ ├── RawPacketList.tsx # Raw packet feed display
│ │ ├── RawPacketList.tsx # Raw packet feed (tertiary debug/observation tool)
│ │ ├── MapView.tsx # Leaflet map showing node locations
│ │ ├── CrackerPanel.tsx # WebGPU channel key cracker (lazy-loads wordlist)
│ │ ├── NewMessageModal.tsx