Files
ai-bot/orac/constants.py
T
swaitsandClaude Opus 4.6 9e6f171c5d feat!: modularize into orac/ package, add Public channel, deduplicate
Split 1424-line monolith into orac/ package with clean separation:
- constants.py — bot identity, radio config, triggers, limits
- crypto.py — Ed25519 identity, channel/peer crypto, ECDH
- meshcore.py — packet parsing/building, routing, dedup, node registry
- ai.py — Claude API integration, query extraction
- state.py — JSON persistence, history tracking
- bot.py — main loop, packet dispatch, connection management

Removed duplicated donglora library code (cobs_frame, read_frame,
encode_radio_config, encode_command, decode_response) — now uses
donglora.send/recv/connect directly.

Added MeshCore Public channel (fixed PSK 8b3387e9...cd72) and
expanded built-in hashtag channels: #nodeops, #repeater-ops, #test,
#weather, #protest, #general, #bot, #net, #emcomm.

Replaced ANSI print() with stdlib logging (color formatter for TTY).
Fixed dependency (donglora-python -> donglora). Strict ruff linting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:34:18 -06:00

97 lines
4.7 KiB
Python

"""Orac bot constants: names, radio config, triggers, limits, paths."""
from __future__ import annotations
from pathlib import Path
# ── Bot identity ─────────────────────────────────────────────────
BOT_NAME: str = "Orac"
# ── Payload limits ───────────────────────────────────────────────
MAX_GRP_TEXT: int = 163 # max bytes for "SenderName: message" in a GRP_TXT
MAX_RESPONSE_CHARS: int = MAX_GRP_TEXT - len(BOT_NAME) - 2 # subtract "Orac: "
# DM plaintext: timestamp(4) + txt_type_attempt(1) + text + padding to 16
# Max payload 184 - dst(1) - src(1) - mac(2) = 180 bytes ciphertext
# 180 bytes ciphertext / 16 = 11 blocks -> 176 bytes plaintext max
# 176 - 5 (header) = 171 bytes text, minus null terminator padding
MAX_DM_TEXT: int = 170
# ── Trigger words ────────────────────────────────────────────────
TRIGGERS: tuple[str, ...] = ("!askai", "!ai", "!claude", "!orac")
AT_MENTIONS: tuple[str, ...] = (f"@{BOT_NAME}".lower(), f"@[{BOT_NAME}]".lower())
# ── History / rate-limit sizing ──────────────────────────────────
CHANNEL_HISTORY_SIZE: int = 20
DM_HISTORY_SIZE: int = 20
RATE_LIMIT_PER_SENDER: float = 5 # seconds, channel messages
RATE_LIMIT_GLOBAL: float = 2 # seconds, any two responses
RATE_LIMIT_DM: float = 2 # seconds, DMs (more interactive)
RATE_LIMIT_REPLY_COOLDOWN: float = 30 # seconds between rate-limit replies
# ── Timers ───────────────────────────────────────────────────────
ADVERT_INTERVAL: float = 7200 # 2 hours in seconds
# ── Paths ────────────────────────────────────────────────────────
DATA_DIR: Path = Path.home() / ".donglora"
KEY_FILE: Path = DATA_DIR / "orac_key.bin"
STATE_FILE: Path = DATA_DIR / "orac_state.json"
# ── Radio ────────────────────────────────────────────────────────
RADIO_CONFIG: dict[str, int] = {
"freq_hz": 910_525_000,
"bw": 6, # 62.5 kHz
"sf": 7,
"cr": 5, # CR 4/5
"sync_word": 0x3444,
"tx_power_dbm": -128, # TX_POWER_MAX
}
# ── AI system prompt ─────────────────────────────────────────────
SYSTEM_PROMPT: str = """\
You are Orac, a terse AI assistant on a MeshCore LoRa radio mesh network.
ABSOLUTE HARD LIMIT: Your ENTIRE response must be {{max_chars}} characters or fewer.
This is a physical constraint of the radio protocol. Every character beyond this limit
is permanently lost and never transmitted. There is NO exception.
Stay well under {{max_chars}}. Aim for 120 or fewer. NEVER show a character count or HTML tags in your reply.
You can see recent messages for context. When someone asks you a question, they may
be referring to the ongoing conversation — use the chat history to understand what they mean.
For example, "what do you think?" refers to whatever was just discussed. "tell me more" means
elaborate on the recent topic. Read the room.
Rules:
- One sentence or short phrase. Never more.
- Emoji OK but don't overdo it. No markdown, no bullet points, no formatting.
- Use common abbreviations freely (e.g., w/, b/c, approx, etc).
- Skip all pleasantries, greetings, and filler.
- You have a dry, sardonic wit. Be helpful but never wordy.
- If the question requires a long answer, give the most important point only.
- NEVER repeat a previous answer. Always find a fresh angle, new phrasing, or different take.
- Be creative and unpredictable. Surprise the reader.\
"""
# ── MeshCore protocol constants ──────────────────────────────────
ADVERT_MAX_AGE: float = 43200 # 12 hours (display only, not key expiry)
PENDING_DM_TTL: float = 300 # 5 minutes
DEDUP_TTL: float = 120 # seconds
DM_DEDUP_TTL: float = 60 # seconds
RESP_SERVER_LOGIN_OK: int = 0x01
# ── Channels CSV path ────────────────────────────────────────────
# Resolved at import time relative to the project root (ai_bot.py's directory).
CHANNELS_CSV: Path = Path(__file__).resolve().parent.parent / "channels.csv"