Commit Graph
32 Commits
Author SHA1 Message Date
snowspeeder 436a26a0fc feat: show album art in the avatar square while music is loaded
The LED wall square switches to the track's cover art whenever a song is
loaded outside a conversation, and returns to the pulsing LED wall while
speaking to the agent (music and conversation are mutually exclusive, so
starting a talk hides the art and ending it restores it).
2026-08-29 17:37:39 -04:00
snowspeeder 68f4c461c8 feat: replace orb avatar with a livekit-style LED wall driven by agent audio
Rebuild & Deploy / rebuild (push) Successful in 37s
- Swap the CSS orb for a 21x21 LED dot matrix on a glass panel, matching
  the livekit.com LED-array hero visual.
- Pulse driven by the agent's rendered audio: an expanding radial bloom
  brightens and widens with remote-audio level, dimmer when listening on
  the mic, faint static matrix when idle, and a subtle warm breathing
  state while thinking.
- State-tinted palettes: cool azure when speaking, blue when listening,
  amber when thinking, dim slate when idle.
- Drop the unused orb CSS-variable pulse plumbing and the presence-breathe
  / orbit-turn keyframes; the wall renders per-frame from the analyser.
2026-08-29 17:05:57 -04:00
snowspeeder e2626ba54e fix: play/pause icon toggle and disappearing conversation controls
Rebuild & Deploy / rebuild (push) Successful in 39s
- The play/pause button never swapped icons: app.js set the .hidden
  property on <svg> elements, which does not reflect to the hidden
  attribute (that IDL attribute is on HTMLElement, not SVGElement), so the
  CSS [hidden] rule never applied and both glyphs overlapped. Toggle a
  .playing class on the button and show/hide the icons via CSS instead.
- The Start conversation and mute buttons were pushed off-screen as the
  transcript grew: the workspace grid had no template-rows, so row 1
  auto-sized to the tall transcript and stretched the voice-console
  beyond the clipped 700px box. Pin the row with grid-template-rows:
  minmax(0,1fr) and give both grid columns min-height:0 so the transcript
  (and the inner #messages) scroll instead of expanding the layout.
2026-08-29 16:56:10 -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 d7c385301e fix: CI deploy parity with compose — media env, volume, healthcheck
Rebuild & Deploy / rebuild (push) Successful in 59s
A missing env var aborts supervisord's %(ENV_x)s config expansion and
takes every program down, which is why the media-player merge failed
CI (run 151): rebuild.yml's docker run did not pass MEDIA_DIR /
MEDIA_SERVER_PORT / YTDLP_COOKIES.

- supervisord.conf: media-server inherits those vars from the container
  environment instead of hard-requiring them (media_server.py has safe
  defaults for all three).
- rebuild.yml: mount the media library volume, pass the media env, and
  extend the healthcheck to the media server healthz.
- compose: YTDLP_COOKIES defaults to the conventional /data/media/
  cookies.txt path (ignored unless the file exists).

Parity verified: container started with CI's exact env (no media vars)
— all five programs RUNNING.
2026-08-29 15:45:54 -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 48b92585c6 fix: harden media server — temp-file yt-dlp stderr, parse guards, UI failure surfacing
Rebuild & Deploy / rebuild (push) Failing after 4m27s
- yt-dlp stderr now goes to a temp file so a chatty stderr can never
  deadlock the stdout progress loop.
- report/search/seek numeric parsing returns 400 instead of 500.
- now-playing card reflects failed/preparing download states.
- media runtime data (library.json, jobs/) is gitignored; only
  .gitkeep is tracked.
2026-08-29 13:27:58 -04:00
snowspeeder f534b8ccb5 chore: ignore media library runtime data, keep directory via .gitkeep 2026-08-29 13:23:21 -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 04c4a53594 feat: media player API, web player panel, and Hope music MCP
- web/media_server.py: FastAPI service owning the permanent library
  (library.json + ./media volume), yt-dlp YouTube Music search, queued
  download jobs with mp3 transcode (ffmpeg), per-room player desired
  state, and an HTTPS proxy for external live-stream URLs.
- agent/media_mcp.py: Hope tools — search_music, play_song,
  offer_song_choices, pause/resume/stop/skip/seek, now_playing,
  list_library, add_stream.
- agent/agent.py: media toolset wiring + conversation rule (music
  playing disables the agent mic; re-engaging pauses music).
- web UI: slide-out Music panel (search, suggestions, library, live
  stream add, play/pause, ±10s, seek, volume) with a reconciling poller
  and media_state data-channel reports.
- token server returns the minted room name; nginx serves /media/files
  as static (native Range) and proxies /media/api to the media server.
- supervisord runs media-server; compose mounts ./media and adds
  ffmpeg to the image.
2026-08-29 13:03:12 -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 a66f09eb28 fix: resolve agent crash and expand settings sheet
Rebuild & Deploy / rebuild (push) Successful in 55s
- Move task_dir assignment before VoiceAssistant construction in
  handle_job; UnboundLocalError was crashing every agent job before it
  joined the room, leaving the UI stuck on 'Opening a secure room'
- Make settings sheet full-window height (100dvh) to match tasks panel
2026-08-29 12:19:00 -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 1fffed8ab3 fix: recover voice UI after text tasks
Rebuild & Deploy / rebuild (push) Successful in 47s
2026-08-29 08:03:59 -04:00
snowspeeder b1643b635d fix: wait for agent before sending text
Rebuild & Deploy / rebuild (push) Successful in 1m2s
2026-08-29 08:00:42 -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 746ca8dca9 fix: define deployment health check
Rebuild & Deploy / rebuild (push) Successful in 28s
2026-08-29 07:09:56 -04:00
snowspeeder 8b88b7646c fix: harden voice rebuild workflow
Rebuild & Deploy / rebuild (push) Failing after 5m13s
2026-08-29 07:04:17 -04:00
snowspeeder f5b84b90e6 chore: trigger gitea runner 2026-08-29 07:00:46 -04:00
snowspeeder 360fc63c20 ci: add rebuild workflow triggered on push to master
Rebuild & Deploy / rebuild (push) Failing after 14s
2026-08-29 06:58:16 -04:00
snowspeeder dac9d195ec fix: clarify voice connection readiness 2026-08-29 06:52:59 -04:00
snowspeeder 16aadcae96 feat: redesign Hope voice experience 2026-08-29 06:51:09 -04:00
snowspeeder 3c4c43f983 fix: use valid LiveKit development secret 2026-08-28 16:30:01 -04:00
snowspeeder 7e15573c10 fix: avoid Unraid agent port collision 2026-08-28 16:27:01 -04:00
snowspeeder c940923539 feat: connect Shane voice sessions to memory 2026-08-28 16:22:18 -04:00
snowspeeder 2a2a2ff768 feat: plan per-user memory integration 2026-08-28 15:59:26 -04:00