Commit Graph
24 Commits
Author SHA1 Message Date
snowspeeder 6b8d55fc92 feat: add Hermes WebUI provisioning and English Dragon HD Omni voices
Rebuild & Deploy / rebuild (push) Successful in 2m17s
2026-10-02 16:07:24 -04:00
snowspeeder 6cb4427210 feat: save account model and thinking settings for Hermes
Rebuild & Deploy / rebuild (push) Successful in 1m9s
2026-10-01 16:38:34 -04:00
snowspeeder a45569f8c1 fix: verify deployment health in Docker host network
Rebuild & Deploy / rebuild (push) Successful in 44s
2026-10-01 16:22:04 -04:00
snowspeeder c5f7bb39e7 fix: delay and vary spoken wait notices
Rebuild & Deploy / rebuild (push) Failing after 7s
2026-10-01 15:59:10 -04:00
snowspeeder 9cc6147c6f test: verify rollback restores originals on partial deployment failure
Rebuild & Deploy / rebuild (push) Failing after 15s
2026-10-01 13:40:21 -04:00
snowspeeder 2aedea84b5 feat: provision private Hermes assistants for authenticated Hope accounts 2026-10-01 13:30:18 -04:00
snowspeeder 436b3fc8b1 refactor: split music streaming into Hope Music
Rebuild & Deploy / rebuild (push) Successful in 2m7s
Move the media server, downloader, library volume, and browser player to the
standalone hope-media-music product. Keep Hope's music tools as a thin HTTP
client of the LAN service on port 8094.

Use a stable voice-room player scope instead of ephemeral LiveKit room names.
The agent now owns the conversation handshake: fresh reported playback mutes
its input, joining pauses music, and the last participant leaving resumes only
music it paused. Peer failures remain non-fatal.

Also move the voice deploy workflow onto the active Unraid Gitea runner and
remove yt-dlp/ffmpeg/media health and volume dependencies from the image.
2026-09-12 06:35:15 -04:00
snowspeeder d4ea4fc984 fix: background tasks now fetch real page content; add task-status tools
Rebuild & Deploy / rebuild (push) Successful in 58s
Background research tasks were producing garbage or failing because the
self-hosted Firecrawl /v1/scrape defaults to a ~30s timeout that's too short
for heavy news/research sites, and the small LLM won't reliably chain a
second scrape call after a search that only returns snippets.

task_worker.py:
- web_search now auto-fetches the top results directly from the container
  (which has working outbound internet) and returns their real text, so the
  model gets actual page content in one call instead of snippet echoes.
- web_scrape requests timeout: 90000 (Firecrawl renders heavy sites but needs
  >30s), and falls back to a direct container fetch with tag-stripping if
  Firecrawl times out or returns nothing.

task_registry.py / dispatch_mcp.py:
- Add task_status(id) and list_tasks() tools so Hope can check the live state
  of any dispatched task (pending / running / completed / failed / STALLED).
- Track last_activity per task and surface a stall heuristic.
- Move the status summary into task_registry (no mcp dependency) so it's
  unit-testable.

agent.py: prompt tells Hope about task_status / list_tasks.
tests: add task-status summary and last-activity regression tests.
2026-08-30 06:54:57 -04:00
snowspeeder df8aa64504 fix: start cached songs even when played by voice (autoplay + room scope)
Rebuild & Deploy / rebuild (push) Successful in 50s
Hope often starts an already-downloaded song from a voice command, with no
click on the player. Two things could leave it paused at 0:

- The <audio> element sits outside the LiveKit room's audio-unlock flow, so
  autoplay policy rejected the poll-driven play(). Capture the first page
  gesture (pointerdown/keydown/touchstart) and play the current src inside it
  to whitelist the element for autoplay.
- Player state is scoped per LiveKit room, but mediaScope() could return a
  stale shell room after a reconnect. Derive it from the connected room.name
  so the browser polls exactly the room the agent joined, and a "play X"
  request can never land in a scope the browser isn't watching.

Add an in-container regression test (test_media_play_resolves_cached_ready_track)
pinning the server-side resolve: an already-ready track plays without a
re-download and serves a stable audio_url.
2026-08-29 17:37:51 -04:00
snowspeeder 4f8eb00c9f feat: progressive music streaming, library-first search, player fixes
Rebuild & Deploy / rebuild (push) Successful in 59s
- Stream downloads as they grow: raw best-audio (no mp3 transcode) served
  chunked from the stream endpoint mid-download, then with native Range
  once complete. Songs play within seconds of hitting Play.
- Fix searched songs never starting: reconcileMedia now wires the stream
  URL when a track that was preparing gains one; audio_url is stable for
  the whole lifecycle so the player never reloads mid-playback.
- Progress bars now parse yt-dlp stderr (stdout never carried [download]).
- Prefer downloaded songs: /media/api/search merges ready library matches
  ahead of YouTube results; search_music/play_song tooling and the agent
  prompt tell Hope to always pick in_library=true.
- Mute button turns solid red while the mic is muted.
2026-08-29 16:41:38 -04:00
snowspeeder d89918fd2a test: stream-proxy test works against remote deployments
Rebuild & Deploy / rebuild (push) Successful in 14s
The fake audio upstream bound the test host's loopback, which the Unraid
media server cannot reach (and its LAN route is firewalled). Local runs
keep the controlled upstream; remote runs use a public reference mp3 and
assert Range semantics without byte equality. docker-exec MCP probes
skip on remote targets (they necessarily exercise the local container).
2026-08-29 16:09:42 -04:00
snowspeeder ce8fd1e046 fix: move media server to 8093 — YOURLS/Apache owns 8092 on xNAS
Rebuild & Deploy / rebuild (push) Successful in 1m0s
The Unraid deployment's /media/api requests were answered by an
Apache/YOURLS stack squatting on 8092, surfacing as UI 503s; the CI
healthcheck (curl without -f) counted that 503 as healthy, so run #15
deployed green against a broken media server.

- media server port 8092 -> 8093 everywhere (server, MCP, agent, nginx,
  compose, CI, .env.example, docs, tests).
- healthchecks use curl -f in compose and rebuild.yml so HTTP error
  statuses fail deployment.
- tests default to the Unraid deployment (VOICE_WEB_URL=
  https://192.168.86.2:9178); token goes through nginx (loopback :8091
  is unreachable off-host); bind-mount-coupled tests skip on remote
  targets. AGENTS.md documents the convention and both failure modes.
2026-08-29 16:01:26 -04:00
snowspeeder 8a7cca456f fix: force tool calls for drifted intents and harden tool-use prompt
Small local models (qwen2.5-vl-3b, gemma-4-e4b) narrate actions ('Sure,
I'll pause the music') without emitting the tool call once the full
system prompt and 26-tool set are in context. Measured: qwen emitted
tools in 2/8 realistic intents, gemma 5/8; single-tool tests pass 100%,
so this is context-induced drift, not plumbing.

- agent/intent_router.py: deterministic forced-call router for
  high-frequency intents — media controls, now_playing/list_library,
  'remember that ...' memory saves, explicit YouTube Music searches,
  and personal-fact recall (memory_recall/search_notes/skill_recall,
  excluded for weather/time questions). Fires only when the tool is
  available; guards against how-do-I questions.
- agent.py: generalize _ForcedToolCallStream to any tool; route through
  intent_router after the dispatch check. System prompt adds a hard
  'call, don't narrate' rule and reconditions 'answer directly'.
- tests/test_intent_router.py: 30 unit tests pinning router behavior.

Verified against the live LLM with the full fidelity harness: combined
forced-or-emitted coverage 8/8 on qwen2.5-vl-3b (was 2/8) and 8/8 on
gemma-4-e4b (was 5/8). Full suite: 79 passed.
2026-08-29 15:38:35 -04:00
snowspeeder a53c090882 docs: media player subsystem architecture and conventions
Document the media server process, /media/ port map, library layout on
the ./media bind mount, Hope's media MCP toolset, the music/mic
mutual-exclusion rule, and yt-dlp download/proxy conventions.
2026-08-29 13:22:01 -04:00
snowspeeder a4ae96f01b feat: default to qwen2.5-vl-3b and cap tool schema length constraints
Rebuild & Deploy / rebuild (push) Successful in 54s
- Default LLM_MODEL to qwen2.5-vl-3b across agent, task worker, tests,
  compose, workflow, and docs
- Cap min/max length-style constraints in tool schemas to 1000 before
  LLM requests; llama.cpp rejects grammar repetitions >= 2000 and the
  memory MCP's update_note body maxLength (200000) broke qwen2.5-vl-3b
  with 'Failed to initialize samplers: failed to parse grammar'
2026-08-29 12:46:20 -04:00
snowspeeder 360e3276b0 feat: switch LLM to OpenWebUI with live model selection
Rebuild & Deploy / rebuild (push) Successful in 3m18s
- Point the agent at OpenWebUI's OpenAI-compatible API (LLM_BASE_URL,
  LLM_API_KEY, LLM_MODEL) with GEMMA_* env fallbacks
- Add list_available_models() fetching {base_url}/models, filtering
  non-chat ids (embedding/reranker)
- Rename GemmaLLM -> HopeLLM with update_options(model=...) for runtime
  model switching (mutates _opts.model) and instance-based compaction
- Data channel: agent publishes {type: models} after connect and on
  get_models; set_model switches live and acks model_changed
- Web UI: Brain section in the settings sheet with a model picker that
  persists the choice in localStorage
- Update tests, CI workflow env, and docs for the new endpoint
2026-08-29 12:09:03 -04:00
snowspeeder 024c42ffb1 fix: require usable recipe task results
Rebuild & Deploy / rebuild (push) Successful in 49s
2026-08-29 08:16:48 -04:00
snowspeeder cb035751d2 fix: route explicit online requests to tasks
Rebuild & Deploy / rebuild (push) Successful in 1m13s
2026-08-29 08:13:03 -04:00
snowspeeder 63e15b6023 fix: restore controls after task completion
Rebuild & Deploy / rebuild (push) Successful in 1m6s
2026-08-29 07:57:23 -04:00
snowspeeder 120ab192f1 feat: add seamless text and voice chat
Rebuild & Deploy / rebuild (push) Successful in 49s
2026-08-29 07:49:29 -04:00
snowspeeder 321421e37f fix: reliably dispatch and return background tasks
Rebuild & Deploy / rebuild (push) Successful in 47s
2026-08-29 07:29:38 -04:00
snowspeeder c940923539 feat: connect Shane voice sessions to memory 2026-08-28 16:22:18 -04:00
Shane c815bcb485 feat: replace custom memory MCP with Cognee remote server
- Remove memory_mcp.py (stdio markdown-file memory)
- Add Cognee MCP as remote Streamable HTTP toolset at 192.168.86.2:8003/mcp
- Filter to only remember/recall/forget tools via allowed_tools
- Update system prompt: Memory section moved to top priority with
  explicit 'call recall FIRST' instructions and examples
- Add COGNEE_MCP_URL env var to docker-compose
- Remove /memory volume mount (no longer needed)
- Rewrite memory tests to use Cognee HTTP client fixture
- 28/28 tests passing
2026-08-23 08:10:42 -04:00
Shane a1d59580f7 fix: ChatContext is not a sequence — use .items for len/list; add test suite
Bug fix:
- GemmaLLM.chat(): len(chat_ctx) → len(chat_ctx.items)
- GemmaLLM._compact_context(): list(chat_ctx) → list(chat_ctx.items)
  ChatContext in livekit-agents 1.7 is not iterable or sized directly;
  it exposes an .items list. The TypeError was silently killing all LLM
  responses (agent heard user but never spoke back).

Test suite (tests/):
- test_web_ui.py: static assets, token endpoint, LiveKit WS proxy
- test_llm_api.py: completion, tool calling, thinking-disabled latency, streaming
- test_mcp_tools.py: weather (Fahrenheit), time, memory, skills — all via
  docker exec + in-container MCP client
- test_agent_integration.py: process alive, worker registered, supervisord,
  container health, LiveKit API
- test_compaction.py: size-triggered compaction, system prompt preservation,
  short-context no-op
- conftest.py: shared fixtures (HTTPS client, token, LLM, docker exec helpers)
- Run with: .venv-tests/bin/python -m pytest tests/ -v
2026-08-22 17:09:29 -04:00