commit
89fbc898f3
1 file changed
+78
-4
+78
-4
@@ -1568,6 +1568,75 @@ def _ensure_full_session_before_mutation(sid: str, session):
|
||||
return full_session
|
||||
|
||||
|
||||
def _get_or_materialize_session(sid: str):
|
||||
"""Get a session, materializing from CLI/agent metadata if not in WebUI store.
|
||||
|
||||
Mirrors the fallback logic in /api/session/archive (routes.py:~8530).
|
||||
Raises:
|
||||
KeyError: session not found in any store
|
||||
PermissionError: session is read-only (messaging/Claude Code)
|
||||
"""
|
||||
try:
|
||||
s = get_session(sid)
|
||||
s = _ensure_full_session_before_mutation(sid, s)
|
||||
return s
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Fallback: try to materialize from CLI/agent session metadata
|
||||
cli_meta = _lookup_cli_session_metadata(sid)
|
||||
if not cli_meta:
|
||||
raise KeyError(sid)
|
||||
|
||||
# Read-only guard: messaging sessions and Claude Code imports cannot be mutated
|
||||
if cli_meta.get("read_only"):
|
||||
raise PermissionError("read-only imported session")
|
||||
|
||||
# Preserve source metadata fields
|
||||
def _apply_source_meta(s):
|
||||
s.is_cli_session = True
|
||||
s.source_tag = cli_meta.get("source_tag")
|
||||
s.raw_source = cli_meta.get("raw_source") or cli_meta.get("source_tag")
|
||||
s.session_source = cli_meta.get("session_source")
|
||||
s.source_label = cli_meta.get("source_label")
|
||||
s.user_id = cli_meta.get("user_id")
|
||||
s.chat_id = cli_meta.get("chat_id")
|
||||
s.chat_type = cli_meta.get("chat_type")
|
||||
s.thread_id = cli_meta.get("thread_id")
|
||||
s.session_key = cli_meta.get("session_key")
|
||||
s.platform = cli_meta.get("platform")
|
||||
|
||||
if _is_messaging_session_record(cli_meta):
|
||||
# Messaging sessions: lightweight Session with no messages (state.db is source of truth)
|
||||
s = Session(
|
||||
session_id=sid,
|
||||
title=cli_meta.get("title") or title_from(get_cli_session_messages(sid), "CLI Session"),
|
||||
workspace=get_last_workspace(),
|
||||
model=cli_meta.get("model") or "unknown",
|
||||
created_at=cli_meta.get("created_at"),
|
||||
updated_at=cli_meta.get("updated_at"),
|
||||
)
|
||||
_apply_source_meta(s)
|
||||
s.save(touch_updated_at=False)
|
||||
else:
|
||||
# Regular CLI/agent sessions: import full message history
|
||||
msgs = get_cli_session_messages(sid)
|
||||
if not msgs:
|
||||
raise KeyError(sid)
|
||||
s = import_cli_session(
|
||||
sid,
|
||||
cli_meta.get("title") or title_from(msgs, "CLI Session"),
|
||||
msgs,
|
||||
cli_meta.get("model") or "unknown",
|
||||
profile=cli_meta.get("profile"),
|
||||
created_at=cli_meta.get("created_at"),
|
||||
updated_at=cli_meta.get("updated_at"),
|
||||
)
|
||||
_apply_source_meta(s)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def _reconcile_stale_stream_state_for_session_rows(session_rows) -> bool:
|
||||
"""Clear stale persisted stream fields before /api/sessions serializes rows."""
|
||||
changed = False
|
||||
@@ -7418,10 +7487,11 @@ def handle_post(handler, parsed) -> bool:
|
||||
except ValueError as e:
|
||||
return bad(handler, str(e))
|
||||
try:
|
||||
s = get_session(body["session_id"])
|
||||
s = _ensure_full_session_before_mutation(body["session_id"], s)
|
||||
s = _get_or_materialize_session(body["session_id"])
|
||||
except KeyError:
|
||||
return bad(handler, "Session not found", 404)
|
||||
except PermissionError:
|
||||
return bad(handler, "Read-only imported sessions cannot be renamed from WebUI", 403)
|
||||
with _get_session_agent_lock(body["session_id"]):
|
||||
from api.session_ops import apply_session_title_rename
|
||||
apply_session_title_rename(s, body["title"])
|
||||
@@ -7608,9 +7678,11 @@ def handle_post(handler, parsed) -> bool:
|
||||
except ValueError as e:
|
||||
return bad(handler, str(e))
|
||||
try:
|
||||
s = get_session(body["session_id"])
|
||||
s = _get_or_materialize_session(body["session_id"])
|
||||
except KeyError:
|
||||
return bad(handler, "Session not found", 404)
|
||||
except PermissionError:
|
||||
return bad(handler, "Read-only imported sessions cannot be updated from WebUI", 403)
|
||||
old_ws = getattr(s, "workspace", "")
|
||||
old_model = getattr(s, "model", None)
|
||||
old_provider = getattr(s, "model_provider", None)
|
||||
@@ -8592,9 +8664,11 @@ def handle_post(handler, parsed) -> bool:
|
||||
except ValueError as e:
|
||||
return bad(handler, str(e))
|
||||
try:
|
||||
s = get_session(body["session_id"])
|
||||
s = _get_or_materialize_session(body["session_id"])
|
||||
except KeyError:
|
||||
return bad(handler, "Session not found", 404)
|
||||
except PermissionError:
|
||||
return bad(handler, "Read-only imported sessions cannot be moved from WebUI", 403)
|
||||
# #1614: refuse moves into a project owned by another profile.
|
||||
target_pid = body.get("project_id") or None
|
||||
if target_pid:
|
||||
|
||||
Reference in new issue
Block a user