fix(tui): store a copy of the selected model, not the caller's object

Merged
RA
2mo ago
+220-155

Issue for this PR

Closes #1364

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

selectModel() in packages/tui/src/context/local.tsx stored the model object it was given with setModelStore("model", agent, model). A Solid store keeps the first object set at a path by reference, and later sets at that path merge their fields into that same object (updatePath โ†’ mergeStoreNode).

Opening a conversation calls restoreSession(msg.model), passing the last user message's recorded model, an object owned by the sync store. When that is the first model stored for the agent in the launch, the model store and the message record share one object. Opening another conversation then merges its model into the first conversation's record, so returning to the first conversation restores the second one's model.

The fix stores a copy ({ providerID, modelID }), so the model store never holds or writes into an object owned by another store. It's a three-line change.

How did you verify your code works?

  • New test test/context/model-store-aliasing.test.tsx. It mounts the real LocalProvider and SyncProvider and feeds two conversations' user messages through message.updated events. It then calls restoreSession() with each conversation's recorded model, as the prompt does, and returns to the first. The test asserts the first conversation's record and the selection on return. It fails on main (the record reads back as the second conversation's model) and passes with this change.

  • In the real TUI, from source, with two conversations recorded on different providers:

    StepRecorded modelSelected in the prompt, mainSelected, this PR
    Open AAcme LargeAcme LargeAcme Large
    Open BAltimate BaseAltimate BaseAltimate Base
    Back to AAcme LargeAltimate BaseAcme Large
  • TUI suite: 339 pass. dialog-scan-gate > "pressing y chooses scan" fails identically on main. Typecheck and the marker/branding guards are clean.

Screenshots / recordings

Text captures are in the table above; the change has no visual component.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

๐Ÿค– Generated with Claude Code

https://claude.ai/code/session_016x3nbZU5Vb6HnSTqE7vELm


Summary by cubic

Fixes cross-conversation model aliasing so returning to a previously opened conversation restores its own model instead of a later one's.

Bug Fixes

  • selectModel() now stores a copy of the model rather than the caller's object; Solid stores keep the first object set at a path by reference and merge later sets into it, so the old code wrote later selections into another conversation's recorded message.
  • Adds a regression test that reproduces the aliasing by restoring two conversations' recorded models in sequence.

Refactors

  • Shares the model-selection test harness and fixtures between the aliasing and stale-Zen cycle tests.

Summary by CodeRabbit

  • Bug Fixes
    • Switching between conversations no longer changes the model information recorded in their messages.