get_listening_history drops the DJ vibe and now playing when history is sparse
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- Mezza giornata
- Idoneità per principianti
- 85/100
Direzione di ricerca
Start in history_outcome in src/infra/dj/tools.rs, where the early return for sparse history (around lines 1111-1117) throws away the vibe and now-playing fields that were already filled in. Move the result building into a small helper that always returns the same structured keys, and add the extra lines only when they are Some. Run cargo test --no-default-features --features telemetry,tui,mcp-server sparse_history and confirm the new tests run (non-zero count) and pass.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
What
The MCP tool set_dj_vibe says the vibe "is stored and returned by get_listening_history". docs/mcp-setup.md says the same. Right now that's only true once you have at least 5 qualifying plays. On a fresh install, get_listening_history gives back one plain sentence. It has no vibe, no now playing and no structured data, so the vibe an agent just set seems to vanish.
How to reproduce it (you don't need Spotify, any build with mcp-server works):
- Build with the
mcp-serverfeature and connect an MCP client, as described indocs/mcp-setup.md. Use a profile with little or no listening history. - Call
set_dj_vibewith{"vibe": "mellow piano"}. - Call
get_listening_history. - You get back only:
Very little listening history so far (0 qualifying plays in the last 30 days). Ask the user what they feel like instead of inferring it.The vibe isn't there, and the structured payload is missing too.
What should happen: the "ask the user" advice stays, and the answer also carries the vibe and the current track when they're set. The structured payload should have the same keys whether history is sparse or not.
Cause: history_outcome copies now_playing and vibe into the summary, then returns early for sparse history with a plain ToolOutcome::ok sentence that throws both away.
Where to look
src/infra/dj/tools.rs, line 192: theset_dj_vibedescription makes the promise: "Either way it is stored and returned by get_listening_history, so you can honour it yourself when you queue."src/infra/dj/tools.rs,fn vibe_effect, lines 1070-1089: the same promise again at lines 1076-1077 (ai-dj build, auto-queue off) and 1086-1087 (build without ai-dj).src/infra/dj/tools.rs,async fn history_outcome, lines 1091-1130:- lines 1097-1102 load the listens file through
spawn_blocking(crate::infra::history::load_listens). This reads the reallistens.jsonlin your state dir. - lines 1104-1109 build the brief and fill in the steer:
let mut summary = brief::build_brief(&listens, period); { let app = app.lock().await; summary.now_playing = super::current_track_label(&app); summary.vibe = app.dj.vibe.clone(); } - lines 1111-1117 are the early return that drops them:
if summary.is_sparse() { return ToolOutcome::ok(format!( "Very little listening history so far ({} qualifying plays in {}). Ask the user what they \ feel like instead of inferring it.", summary.total_plays, summary.period_label )); } - lines 1119-1129 handle the full path: a
json!withperiod,total_plays,top_artists,top_tracks,top_albums,recent,now_playing,vibe, returned asToolOutcome::with_data(summary.to_prompt_block(), structured).
- lines 1097-1102 load the listens file through
src/infra/dj/tools.rs,now_playing_outcome, lines 1160-1162: the comment there explains why every branch should use the same payload keys. A missing key gets read as "unknown". Follow the same rule here.src/infra/dj/tools.rs, around line 502:ToolOutcome { text, structured: Option<Value>, is_error }, with the constructorsokandwith_datajust below it.src/infra/dj/brief.rs, line 25:#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct TasteBrief. It derivesDefault, so you can build one by hand in a test.src/infra/dj/brief.rs, lines 135-159:to_prompt_block. Lines 152-157 hold the "Currently playing" and "The listener asked for" wording. Reuse that wording.src/infra/dj/brief.rs, line 164:is_sparseisself.total_plays < 5, so every new user takes the sparse branch.docs/mcp-setup.md, lines 253-257: the user-facing promise (key sentence at 256-257). No doc change is needed once the code keeps that promise.
Possible solution
- Move everything after the lock block into a small sync helper, for example:
Then/// The tool answer for a filled-in brief, sparse or not. fn history_result(summary: &TasteBrief) -> ToolOutcome { let structured = json!({ "period": summary.period_label, "total_plays": summary.total_plays, "top_artists": summary.top_artists, "top_tracks": summary.top_tracks, "top_albums": summary.top_albums, "recent": summary.recent, "now_playing": summary.now_playing, "vibe": summary.vibe, }); if summary.is_sparse() { let mut text = format!( "Very little listening history so far ({} qualifying plays in {}). Ask the user what they \ feel like instead of inferring it.", summary.total_plays, summary.period_label ); // append "Currently playing: ..." and "The listener asked for: ..." when Some return ToolOutcome::with_data(text, structured); } ToolOutcome::with_data(summary.to_prompt_block(), structured) }history_outcomeends withhistory_result(&summary). - Keep the "Ask the user what they feel like" sentence. It's the reason the sparse branch exists: with no history the agent shouldn't guess a taste. Only add the vibe and now-playing lines, and only when they're
Some. - Build the
structuredjson once and use it in both branches, so a client always sees the same keys. On the sparse path the lists are just empty, which is fine. - Use the same wording as
to_prompt_block(brief.rs lines 152-157) for the two extra lines so the model sees one consistent phrasing. You can either copy the format strings or pull them into a tiny shared function inbrief.rs. Pick whichever keeps the diff smaller. - Traps:
- Don't call
history_outcomefrom a test. It reads the reallistens.jsonlfrom your state dir and needs anArc<Mutex<App>>. Test the new helper instead. - The helper must be called from
history_outcome, not only from tests, or the non-test build warns about dead code. period_labelis&'static str, so a hand-built brief uses a literal such as"the last 30 days".tools.rsonly compiles underdj-core(src/infra/mod.rsgatespub mod djon it), andmcp-serverpulls that in. You don't need an extra#[cfg]on the tests.- The in-TUI DJ can call this tool too (its spec row has
dj: true). The extra lines then reach that model's prompt as well, which is harmless: it's the listener's own steer. - Don't change
is_sparseor the threshold of 5.
- Don't call
- Tests to add, in the existing
#[cfg(test)] mod testsat the bottom ofsrc/infra/dj/tools.rs(it already hasuse super::*;):sparse_history_still_returns_the_vibe: buildTasteBrief { period_label: "the last 30 days", total_plays: 0, vibe: Some("mellow piano".into()), ..Default::default() }, call the helper, and assertoutcome.text.contains("mellow piano"),outcome.text.contains("Ask the user")andoutcome.structured.unwrap()["vibe"] == json!("mellow piano").sparse_history_still_returns_now_playing: same idea withnow_playing: Some("Song by Artist".into()).sparse_history_payload_has_the_same_keys_as_full_history: withvibe: None, assertstructured["vibe"].is_null()and thattop_artists,total_playsandperiodare present.- Optionally
full_history_still_uses_the_prompt_block:total_plays: 10with one top artist, and assert the text equalssummary.to_prompt_block(). - None of these need a Spotify account, network or audio device. The brief is plain data.
- CI runs clippy on the bin target only, so run cargo test to compile test code (when tests are added).
- Leave tools/gates.count alone, new tests need no counter change.
Done when
cargo test --no-default-features --features telemetry,tui,mcp-server sparse_historyruns your new tests and they pass. A filter that matches nothing still exits 0, so check the count.- Optional manual check (no Premium needed): run an
mcp-serverbuild with a fresh profile, callset_dj_vibewith{"vibe": "mellow piano"}, thenget_listening_history, and see the vibe in both the text and the structured result. - cargo fmt --all -- --check, cargo clippy --no-default-features --features telemetry,tui -- -D warnings and cargo test --no-default-features --features telemetry,tui pass, plus
cargo clippy --no-default-features --features telemetry,tui,mcp-server -- -D warningsand themcp-servertest run above.
- Lingua principale
- Rust
- Stelle
- 1.4k
- Fork
- 238
- Merge medio
- 9h 21m
- PR unite (30g)
- 193
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di LargeModGames/spotatui
-
enhancement good first issue javascript
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
LargeModGames/spotatui#841 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
documentation good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
LargeModGames/spotatui#832 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
LargeModGames/spotatui#829 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
LargeModGames/spotatui#827 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug enhancement good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
LargeModGames/spotatui#825 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di LargeModGames/spotatui
Issue simili
-
documentation good first issue
Difficoltà 2/5 Mezza giornata Idoneità per principianti 72/100
sujithputta02/Esapay#13 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
tursodatabase/turso#10002 ·
I maintainer di solito rispondono entro 1 giorno
-
needs triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
topgrade-rs/topgrade#2419 ·
I maintainer di solito rispondono entro 1 giorno
-
review-bot
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
midagedev/bloomery#318 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
Chia-Network/clvm_rs#863 ·
I maintainer di solito rispondono entro 1 giorno