Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/agents/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Deep subsystem reference for AI assistants. Open this when a task touches the Ch

- **Components:** `ChatPanel.tsx` (channel/DM UI) + shared `ChatComposer.tsx` (drafts, mentions, chunking, spellcheck, emoji; also used by `RoomsPanel.tsx`). Reticulum DM **Share as paper** / **Scan paper** via `ChatDmPaperControls.tsx` + `createReticulumPaperMessage.ts`. Scroll-at-bottom helper: `chatScrollUtils.ts` (`getDistFromChatBottom`).
- **Composer limits / send cadence:** `chatComposerLimits.ts` — `getMaxChunks(protocol)` via `ProtocolCapabilities.composerMaxChunks` (MeshCore = 1: no outbound `[i/N]` split; `splitChatMessage` returns `null` when text needs more than one packet), room payload via `getMeshcoreRoomPayloadLimit`, `computeComposerLimitStatus` phases (`warn` surfaces a single-packet ⓘ hint; `overMaxSingle` disables send and shows a `role="note"` callout). MeshCore also gets a **non-blocking** ~5s "sending too fast" advisory (`role="status"`, dismissible) from an app-wide clock in `meshcoreSendRateNotice.ts` (`recordMeshcoreSend` / `isMeshcoreSendTooFast`, `MESHCORE_FAST_SEND_WARN_INTERVAL_MS`). Every **live** MeshCore send advances the clock and can show the advisory — text (`handleSend`), GIF, and share-location in `ChatComposer`, plus outbox drain (`useChatOutbox.ts`). Legacy MeshCore outbox rows with `groupTotal > 1` or `[i/N]` payloads are quarantined (`blocked`) on drain instead of being transmitted. Inbound multi-part `[i/N]` merge is unchanged. i18n: `chatPanel.composeLimit.meshcoreSingleNotice.*`, `chatPanel.meshcoreFastSend.warning`, `chatPanel.outboxLegacyMultipartBlocked`. See [`meshcore-meshtastic-parity.md`](../meshcore-meshtastic-parity.md).
- **Payload / links:** `ChatPayloadText.tsx` — mention highlighting, search marks, URL linkification; link previews via `chat:fetchLinkPreview` (`src/main/fetchLinkPreview.ts`): Open Graph for HTML pages; **YouTube** watch/shorts/youtu.be via oEmbed + thumbnail; **direct image URLs** (path extension via `chatDirectImageUrl.ts` or raster `Content-Type`) return `kind: 'image'` and render as inline embeds (`ChatInlineImage` / `DirectImageEmbed`); OG/YouTube use card layout. Security: DNS-pinned undici `Agent`, private/loopback blocked, magic-byte MIME sniff (`safeRasterImageMime.ts`), HTTPS-only image embeds, 10s fetch / 3s DNS, 64 KiB HTML cap, **2 MiB** image fetch cap (256 KiB cache payload cap), LRU caches, single-flight dedup (renderer map capped). Previews load even when scrolled up. LXMF attachment rasters: `chat:readReticulumAttachmentAsDataUrl` (`reticulum-attachment-image.ts`; path jail, magic-byte MIME, SVG rejected, 2 MiB, IPC rate limit) → `ReticulumAttachmentLine`. Reply quotes: `replyPreview.ts`.
- **Payload / links:** `ChatPayloadText.tsx` — mention highlighting, search marks, URL linkification; link previews via `chat:fetchLinkPreview` (`src/main/fetchLinkPreview.ts`): Open Graph for HTML pages; **YouTube** watch/shorts/youtu.be via oEmbed + thumbnail; **direct image URLs** (path extension via `chatDirectImageUrl.ts` or raster `Content-Type`) return `kind: 'image'` and render as inline embeds (`ChatInlineImage` / `DirectImageEmbed`); OG/YouTube use card layout. Security: DNS-pinned undici `Agent`, private/loopback blocked, magic-byte MIME sniff (`safeRasterImageMime.ts`), HTTPS-only image embeds, 10s fetch / 3s DNS, 64 KiB HTML cap, **2 MiB** image fetch cap (256 KiB cache payload cap), LRU caches, single-flight dedup (renderer map capped). Previews load even when scrolled up. LXMF attachment rasters: `chat:readReticulumAttachmentAsDataUrl` (`reticulum-attachment-image.ts`; path jail, magic-byte MIME, SVG rejected, 2 MiB, IPC rate limit) → `ReticulumAttachmentLine`. LXMF voice memos (`hasReticulumVoiceMemo`): Chat DM mic → sidecar `/api/v1/voice/memo/*` via `electronAPI.reticulum.voiceMemo.*`; playback via `chat:readReticulumAttachmentBytes` (`reticulum-attachment-audio.ts`; OggS sniff, 256 KiB) → `ReticulumVoiceMemoLine`. Reply quotes: `replyPreview.ts`.
- **Storage helpers:** `src/renderer/lib/chatPanelProtocolStorage.ts` — drafts (`mesh-client:drafts:<protocol>`), open DM tabs, last-read, per-view mute (`mesh-client:mutedViews:<protocol>`), starred (`mesh-client:starred:<protocol>`, cap 200), MeshCore flood-scope overrides per chat view (`mesh-client:floodScopeOverrides:<protocol>`, channel or DM `viewKey`).
- **Notifications:** `src/renderer/lib/chatNotifications.ts` — `playMessageNotification(type)` via Web Audio: `channel` = single 880 Hz pulse (150 ms); `dm` / `reply` = dual pulse (587.33 Hz then 783.99 Hz, 50 ms each, 35 ms gap). Resumes suspended `AudioContext` when the window is hidden/minimized. Type selection in `chatUnreadCounts.ts` (`resolveChatNotificationType`, `pickAudibleNotificationType`; batch priority reply > dm > channel). **ChatPanel** plays when the user is on Chat but reading another view; **App** plays for other panels / backgrounded window (avoids double beep). Meshtastic hidden-window desktop notifications are visual-only (`silent: true` in `meshtasticRouterSideEffects.ts`); typed Web Audio from App owns sound. Global mute `mesh-client:notifMuted`; per-view mute in `mutedViews`. Main-process **tray** icon shows unread when chat or MeshCore Rooms traffic arrives while backgrounded (`src/main/index.ts` `buildTrayIcon`).
- **Meshtastic dedup:** `meshtasticMessageDedup.ts` — merges delayed RF/MQTT duplicates (**10-minute** content window) in `useMeshtasticRuntime` ingest.
Expand Down
3 changes: 2 additions & 1 deletion docs/agents/reticulum.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ Deep subsystem reference for AI assistants. Open this when a task touches the Re
- **Nomad errors:** `lib/nomad/nomadPageErrorHumanize.ts` (sidecar error codes → i18n); LinkClient Nomad overlay in `reticulum-sidecar/patches/`
- **Nomad request bodies:** MessagePack `field_*`/`var_*` encode/decode lives in rsNomad `nomad-core` (`encode_request_fields` / `decode_request_fields`); sidecar `nomad_request_payload.rs` only unwraps HTTP base64 JSON before calling encode
- **LXST voice:** `hasLxstVoice` gates Call buttons (Peers + Chat DM). Session helpers in `reticulumVoiceSession.ts` (dial/answer/hangup + mic PCM); UI store `reticulumVoiceStore.ts`; overlay `ReticulumVoiceOverlay` (App mount). Dedicated IPC `reticulum:voiceSendAudio` + push channel `reticulum:voiceAudio` (`/ws/voice`; preload `onVoiceAudio`); control via `electronAPI.reticulum.voice.*`. Runtime WS: `voice.update` / `voice.incoming` / `voice.stats` / `voice.terminated` / `voice.error` (errors should carry `link_id` when known; match by link/generation/remote). **Establish-only media:** Answer warms AudioContext; mic capture/TX starts only after `established`; sidecar soft-drops pre-establish PCM (`not_established`). Outbound progress tones: dial → peer DTMF fold → UK double-ring (`reticulumVoiceCallTones.ts` / `reticulumVoiceOutcome.ts` / `reticulumVoiceFeedback.ts`); media-start coalesces by `callGeneration` to avoid Answer mic thrash. Terminal reasons: treat sidecar `established`/`terminated` as completed (not fail).
- **LXMF voice memos:** `hasReticulumVoiceMemo` gates Chat DM mic (not LXST Call). Sidecar encodes Opus/`AM_OPUS_OGG` via `/api/v1/voice/memo/{start,audio,stop,cancel}` + dedicated IPC `electronAPI.reticulum.voiceMemo.*` (`reticulum:voiceMemoStart` / `voiceMemoSendAudio` / `voiceMemoStop` / `voiceMemoCancel`; proxy blocked). Capture resamples to 24 kHz / 60 ms frames and serializes PCM IPC before stop. Ogg capped ~240 KiB under the 256 KiB LXMF field / default PN deposit. Key files: `reticulumVoiceMemo.ts`, `sendReticulumVoiceMemo.ts`, `reticulumVoiceMemoStore.ts`, `reticulum-attachment-audio.ts`, `ReticulumVoiceMemoLine.tsx`. Ingest stamps `FIELD_AUDIO` → attachment jail + playback (`chat:readReticulumAttachmentBytes`). Oversize-for-PN emits `message_too_large_for_propagation` (notice toast + Direct-only badge — never a PN-outage toast). Tap mic to record, tap again to send; Esc/DM-switch cancels; mic stays visible while recording even if the draft is non-empty.
- **LRGP games:** `hasLrgpGames` gates Games tab + Challenge (Peers / Chat DM). Sidecar `games_session` + `LrgpStore`; companion `games_outbound.db` persists last envelope + `delivery_state` (LXMF outbound bridge → session chips / Resend). Dedicated IPC `electronAPI.reticulum.games.*` / `reticulum:games*` (proxy rejects `/api/v1/games/*`); WS `games.update` / `games.action_result`. Parity: [../reticulum-games-parity.md](../reticulum-games-parity.md).
- **Gating:** `hasReticulumDiscoveryMap` (Map tab); `hasReticulumRemotePanel` / `hasRncpTransfer` (Remote tab + Chat DM rncp); `hasRrcPanel` (RRC tab); `hasLxstVoice` (LXST Call); `hasLrgpGames` (Games); `hasReticulumInterfaceConfig` / `hasReticulumNetworkPanel` / `ProtocolCapabilities`
- **Gating:** `hasReticulumDiscoveryMap` (Map tab); `hasReticulumRemotePanel` / `hasRncpTransfer` (Remote tab + Chat DM rncp); `hasRrcPanel` (RRC tab); `hasLxstVoice` (LXST Call); `hasReticulumVoiceMemo` (LXMF voice memos); `hasLrgpGames` (Games); `hasReticulumInterfaceConfig` / `hasReticulumNetworkPanel` / `ProtocolCapabilities`
- **rnsh/rncp:** sidecar `stack/{rnsh_session,rncp_transfer,path_speed,link_task}.rs` + HTTP `/api/v1/rnsh/*`, `/api/v1/rncp/*`, `/api/v1/remote/*`; typed `electronAPI.reticulum.rnsh|rncp|remote`; picker-gated send/fetch paths in `reticulum-remote-paths.ts`; LXMF enable-request sentinel `mesh-client:request-rncp-receive:v1` (`rncpRequestEnable.ts`); peer reply `mesh-client:rncp-receive-dest:v1:<hash>` autofills via `applyRncpReceiveDestShare` (prefer pending from `markRncpReceiveDestSharePending` / `sendRncpRequestEnable`; still apply without pending for older peers); enable-request modal + dest-share side effects deduped by LXMF `message_hash` (`rncpLxmfControlSideEffectDedup`) so catch-up cannot re-fire; already-listening auto-share is once per peer per request-enable cooldown; inbound listener config persists (`rncp_listener_*` in `mesh_client_stack.json`) and restores on live stack start
- **Runtime:** `useReticulumRuntime`, `lib/sessions/reticulumSession.ts`, `lib/ingest/reticulumIngest.ts`; connect starts sidecar, not `ConnectionDriver` RF — marks **configured** when HTTP + identity ready (live attach may still run); `RETICULUM_CONFIGURED_EVENT` wakes RRC. Cancel/stop is fire-and-forget vs cargo/BLE (`START_ABORTED` checkpoints; next start does not rejoin a doomed promise). LXMF/RRC proxy sends: **15 s** `RETICULUM_IPC_SEND_TIMEOUT_MS`. RRC auto-connect (`useRrcStartupAutoConnect`): ~**500 ms** while hubs pending, ~4 s steady. Sidecar RRC: `rrc_codec` / `rrc_link` / `rrc_session` / `api/rrc.rs`
- **Diagnostics:** `ReticulumDiagnosticEngine.ts` (Reticulum-native rows; no LoRa hop-goblin semantics) — includes `reticulum/sidecar-unhealthy` (60s grace; HTTP health, not listen-first ready lag), `reticulum/rns-not-ready` / `reticulum/lxmf-not-ready`, `reticulum/propagation-sync-stuck`, `reticulum/propagation-sync-failing` (1h TTL)
Expand Down
Loading