Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled

Transform isair/jarvis into a Discord-controlled voice assistant running on
the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact.

- bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral),
  voice channel join + voice receive/playback, pluggable VNC screen broadcast
  (selfbot live / noVNC / screenshot)
- bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS
  behind a thin localhost HTTP API
- .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite,
  docs/language-comparison.md and docs/vnc-xfce-setup.md

Language decision: hybrid (Python brain + Node/bun Discord layer) because
Discord blocks bot video; native screen broadcast only works via a Node
selfbot library.
This commit is contained in:
javis-bot
2026-06-09 14:51:05 +09:00
parent a5bf8d1826
commit c4abf63f38
308 changed files with 94135 additions and 1 deletions

View File

@@ -0,0 +1,63 @@
"""Tests for tool-router model resolution order.
The reply engine and the listener warmup path both need to pick the model
used for LLM-based tool selection, and they MUST pick the same one — if they
diverge, warmup loads the wrong model and the first real routing call eats a
cold-start stall. The resolution order is enforced by a single helper
(``resolve_tool_router_model``), which this test exercises directly.
Order: `tool_router_model` → `intent_judge_model` → `ollama_chat_model` →
empty string. The key property is that an explicit `tool_router_model` wins
over everything, and that an empty `tool_router_model` falls through to the
(small, fast, already-warm) judge model BEFORE the (large, slow) chat model.
"""
import pytest
from jarvis.reply.engine import resolve_tool_router_model
class _Cfg:
"""Minimal cfg stand-in with only the attributes the resolver reads."""
def __init__(self, router="", judge="", chat=""):
self.tool_router_model = router
self.intent_judge_model = judge
self.ollama_chat_model = chat
class TestToolRouterModelResolution:
@pytest.mark.unit
def test_explicit_router_wins(self):
cfg = _Cfg(router="custom-router", judge="judge-m", chat="chat-m")
assert resolve_tool_router_model(cfg) == "custom-router"
@pytest.mark.unit
def test_empty_router_falls_through_to_judge(self):
"""The whole point of the helper: an unset tool_router_model must
pick the judge model, not the chat model. This is what keeps the
routing call on the small, warm model instead of reloading the
large chat model every turn."""
cfg = _Cfg(router="", judge="judge-m", chat="chat-m")
assert resolve_tool_router_model(cfg) == "judge-m"
@pytest.mark.unit
def test_falls_through_to_chat_when_no_router_or_judge(self):
cfg = _Cfg(router="", judge="", chat="chat-m")
assert resolve_tool_router_model(cfg) == "chat-m"
@pytest.mark.unit
def test_returns_empty_when_nothing_configured(self):
"""The caller handles an empty model name by falling back to the
all-tools path — the helper itself should not invent a default."""
cfg = _Cfg(router="", judge="", chat="")
assert resolve_tool_router_model(cfg) == ""
@pytest.mark.unit
def test_robust_to_missing_attributes(self):
"""When a cfg-like object is missing an attribute entirely (as can
happen for partial mocks), the resolver must not raise."""
class Partial:
ollama_chat_model = "only-chat"
assert resolve_tool_router_model(Partial()) == "only-chat"