fix(workspace): new chat inherits the current conversation's workspace (#7180)
* fix(workspace): new chat inherits the current conversation's workspace
`newSession()` resolved the workspace of a new chat as:
switchWs || S._profileDefaultWorkspace || S.session.workspace
`S._profileDefaultWorkspace` is global to the profile, hydrated from
`/api/profile/active`. Preferring it over the current conversation meant a new
chat opened from a MES conversation landed on the profile-global value instead
of MES. With several conversations running in parallel on different workspaces,
the context the user was actually in was systematically lost.
The precedence is worse than it looks, because that "profile default" is not a
stable configured value: `get_profile_default_workspace()` reads
`last_workspace.txt` BEFORE the configured key (api/workspace.py ~419-427), and
for the `default` profile that file is the GLOBAL one. `set_last_workspace()`
rewrites it on every `/api/chat/start` and `/api/session/update` of ANY
conversation. So "profile default wins over the current session" degrades in
practice to "whatever workspace another conversation last used wins over the
conversation I am in" -- observed live: the pointer flipped mid-operation
because a concurrent turn wrote to it.
New precedence:
switchWs || S.session.workspace || S._profileDefaultWorkspace
Preserved invariants:
- the one-shot profile-switch flag still wins first and is still consumed (#823);
- a blank page (no loaded session) still falls back to the profile default,
which remains required for the empty-composer workspace chip (#804/#5169);
- `_profileDefaultWorkspace` is never nulled by `newSession()` (#823).
This deliberately diverges from upstream #4755, which made the profile default
win. That decision assumed a stable configured default; on a shared/global
`last_workspace.txt` it inverts its own intent. The upstream test is rewritten
with the rationale inline so a future rebase does not silently restore the bug,
plus a new test that evaluates the real expression under node and proves the
blank-page fallback still holds.
Pre-existing failures in tests/test_workspace_stale_recovery.py (3) were
verified identical with and without this change.
* fix(workspace): recover stale inherited workspace
---------
Co-authored-by: Anthony Ruiz <ruizanthony@users.noreply.github.com>
This commit is contained in:
1 parent
1f5e45527a
commit
eee72f942e
4 files changed
+276
-8
No files matched your search
@@ -120,7 +120,11 @@ def _new_session_driver(session_workspace: str, default_workspace: str, switch_w
|
||||
function loadDir(){{return Promise.resolve();}}
|
||||
{new_session}
|
||||
newSession().then(()=>{{
|
||||
process.stdout.write(JSON.stringify({{captured,switchWorkspace:S._profileSwitchWorkspace}}));
|
||||
process.stdout.write(JSON.stringify({{
|
||||
captured,
|
||||
switchWorkspace:S._profileSwitchWorkspace,
|
||||
profileDefaultWorkspace:S._profileDefaultWorkspace,
|
||||
}}));
|
||||
}}).catch(err=>{{
|
||||
console.error(err && err.stack || err);
|
||||
process.exit(1);
|
||||
@@ -130,7 +134,33 @@ def _new_session_driver(session_workspace: str, default_workspace: str, switch_w
|
||||
|
||||
|
||||
@node_test
|
||||
def test_new_session_prefers_profile_default_over_current_session_workspace():
|
||||
def test_new_session_prefers_current_session_workspace_over_profile_default():
|
||||
"""Deliberate divergence from the precedence introduced in #4755.
|
||||
|
||||
#4755 made the "profile default" win over the current conversation's workspace.
|
||||
That precedence assumes the profile default is a stable, configured value. It is
|
||||
not, for a structural reason:
|
||||
|
||||
``api/workspace.py::get_profile_default_workspace()`` reads ``last_workspace.txt``
|
||||
BEFORE the configured value, and for the ``default`` profile that file is the
|
||||
GLOBAL one. ``set_last_workspace()`` rewrites it on every ``/api/chat/start`` and
|
||||
``/api/session/update`` from ANY conversation.
|
||||
|
||||
So ``S._profileDefaultWorkspace`` — hydrated from ``/api/profile/active`` — is not a
|
||||
configured default: it is a volatile shared pointer. "profile default wins over the
|
||||
current conversation" therefore degrades into "the workspace last used by ANOTHER
|
||||
conversation wins over the conversation I am actually in". With several
|
||||
conversations open on different workspaces, opening a new chat from conversation A
|
||||
created it on conversation B's workspace.
|
||||
|
||||
Precedence used here: one-shot profile switch -> current conversation -> profile
|
||||
default. The profile default remains the blank-page fallback (#804/#5169) and is
|
||||
never consumed (#823).
|
||||
|
||||
MAINTAINER NOTE: this intentionally diverges from #4755 and will conflict if that
|
||||
precedence is restored. Please do not "fix" it by reinstating the previous order
|
||||
without first addressing the volatility of ``get_profile_default_workspace()``.
|
||||
"""
|
||||
payload = _run_node(_new_session_driver(
|
||||
session_workspace="/current-workspace",
|
||||
default_workspace="/profile-default",
|
||||
@@ -138,8 +168,29 @@ def test_new_session_prefers_profile_default_over_current_session_workspace():
|
||||
))
|
||||
|
||||
assert payload["captured"]["path"] == "/api/session/new"
|
||||
assert payload["captured"]["body"]["workspace"] == "/profile-default"
|
||||
assert payload["captured"]["body"]["workspace"] == "/current-workspace"
|
||||
assert payload["captured"]["body"]["prev_session_id"] == "previous-session"
|
||||
assert payload["captured"]["body"]["workspace_inherited_from_prev_session"] is True
|
||||
assert payload["profileDefaultWorkspace"] == "/profile-default"
|
||||
|
||||
|
||||
@node_test
|
||||
def test_new_session_blank_page_falls_back_to_profile_default():
|
||||
"""Sans conversation chargée, le défaut de profil reste le repli (#804/#5169)."""
|
||||
driver = _new_session_driver(
|
||||
session_workspace="/current-workspace",
|
||||
default_workspace="/profile-default",
|
||||
switch_workspace=None,
|
||||
).replace(
|
||||
"session:{session_id:'previous-session',workspace:\"/current-workspace\"},",
|
||||
"session:null,",
|
||||
)
|
||||
payload = _run_node(driver)
|
||||
|
||||
assert payload["captured"]["body"]["workspace"] == "/profile-default"
|
||||
assert "workspace_inherited_from_prev_session" not in payload["captured"]["body"]
|
||||
assert payload["profileDefaultWorkspace"] == "/profile-default"
|
||||
|
||||
|
||||
|
||||
@node_test
|
||||
@@ -151,7 +202,28 @@ def test_new_session_one_shot_switch_workspace_still_wins_and_clears():
|
||||
))
|
||||
|
||||
assert payload["captured"]["body"]["workspace"] == "/explicit-switch"
|
||||
assert "workspace_inherited_from_prev_session" not in payload["captured"]["body"]
|
||||
assert payload["switchWorkspace"] is None
|
||||
assert payload["profileDefaultWorkspace"] == "/profile-default"
|
||||
|
||||
|
||||
@node_test
|
||||
def test_parallel_conversations_each_inherit_their_own_workspace():
|
||||
first = _run_node(_new_session_driver(
|
||||
session_workspace="/workspace-a",
|
||||
default_workspace="/shared-profile-pointer",
|
||||
switch_workspace=None,
|
||||
))
|
||||
second = _run_node(_new_session_driver(
|
||||
session_workspace="/workspace-b",
|
||||
default_workspace="/shared-profile-pointer",
|
||||
switch_workspace=None,
|
||||
))
|
||||
|
||||
assert first["captured"]["body"]["workspace"] == "/workspace-a"
|
||||
assert second["captured"]["body"]["workspace"] == "/workspace-b"
|
||||
assert first["captured"]["body"]["workspace_inherited_from_prev_session"] is True
|
||||
assert second["captured"]["body"]["workspace_inherited_from_prev_session"] is True
|
||||
|
||||
|
||||
@node_test
|
||||
|
||||
Reference in new issue
Block a user