fix(#4373): mark process-wakeup turns as synthetic and filter from visible transcript
This commit is contained in:
1 parent
6f233d3a20
commit
d85979cd69
4 files changed
+136
-2
No files matched your search
+6
-1
@@ -416,6 +416,9 @@ def _append_recovered_pending_turn(session, *, timestamp: int | None = None) ->
|
||||
'timestamp': recovered_ts,
|
||||
'_recovered': True,
|
||||
}
|
||||
pending_source = getattr(session, 'pending_user_source', None)
|
||||
if pending_source and pending_source != 'webui':
|
||||
recovered['_source'] = pending_source
|
||||
if session.pending_attachments:
|
||||
recovered['attachments'] = list(session.pending_attachments)
|
||||
session.messages.append(recovered)
|
||||
@@ -621,6 +624,7 @@ class Session:
|
||||
pending_user_message: str=None,
|
||||
pending_attachments=None,
|
||||
pending_started_at=None,
|
||||
pending_user_source: str=None,
|
||||
context_messages=None,
|
||||
compression_anchor_visible_idx=None,
|
||||
compression_anchor_message_key=None,
|
||||
@@ -668,6 +672,7 @@ class Session:
|
||||
self.pending_user_message = pending_user_message
|
||||
self.pending_attachments = pending_attachments or []
|
||||
self.pending_started_at = pending_started_at
|
||||
self.pending_user_source = pending_user_source
|
||||
self.context_messages = context_messages if isinstance(context_messages, list) else []
|
||||
self.compression_anchor_visible_idx = compression_anchor_visible_idx
|
||||
self.compression_anchor_message_key = compression_anchor_message_key
|
||||
@@ -743,7 +748,7 @@ class Session:
|
||||
'input_tokens', 'output_tokens', 'estimated_cost',
|
||||
'cache_read_tokens', 'cache_write_tokens',
|
||||
'personality', 'active_stream_id',
|
||||
'pending_user_message', 'pending_attachments', 'pending_started_at',
|
||||
'pending_user_message', 'pending_attachments', 'pending_started_at', 'pending_user_source',
|
||||
'compression_anchor_visible_idx', 'compression_anchor_message_key',
|
||||
'compression_anchor_summary', 'pre_compression_snapshot',
|
||||
'context_engine', 'compression_anchor_engine', 'compression_anchor_mode',
|
||||
|
||||
+10
-1
@@ -13908,7 +13908,7 @@ def _handle_background(handler, body):
|
||||
return j(handler, {"task_id": task_id, "stream_id": stream_id, "session_id": bg.session_id})
|
||||
|
||||
|
||||
def _checkpoint_user_message_for_eager_session_save(s, msg: str, attachments, started_at: float | None) -> None:
|
||||
def _checkpoint_user_message_for_eager_session_save(s, msg: str, attachments, started_at: float | None, source: str = "webui") -> None:
|
||||
"""Materialize the current user turn for eager first-turn persistence.
|
||||
|
||||
The streaming thread still receives ``pending_user_message`` so existing
|
||||
@@ -13926,6 +13926,8 @@ def _checkpoint_user_message_for_eager_session_save(s, msg: str, attachments, st
|
||||
if latest_text == msg_text:
|
||||
return
|
||||
user_msg = {"role": "user", "content": msg}
|
||||
if source and source != "webui":
|
||||
user_msg["_source"] = source
|
||||
if isinstance(started_at, (int, float)) and started_at > 0:
|
||||
user_msg["timestamp"] = int(started_at)
|
||||
if attachments:
|
||||
@@ -13963,6 +13965,7 @@ def _prepare_chat_start_session_for_stream(
|
||||
model_provider,
|
||||
stream_id: str,
|
||||
started_at: float | None = None,
|
||||
source: str = "webui",
|
||||
):
|
||||
"""Persist chat-start state according to webui.session_save_mode.
|
||||
|
||||
@@ -13980,6 +13983,7 @@ def _prepare_chat_start_session_for_stream(
|
||||
s.pending_user_message = msg
|
||||
s.pending_attachments = attachments
|
||||
s.pending_started_at = started_at if started_at is not None else time.time()
|
||||
s.pending_user_source = source
|
||||
current_title = getattr(s, "title", None)
|
||||
if _is_default_or_empty_session_title(current_title):
|
||||
provisional_title = _provisional_title_from_prompt(msg, current_title or "Untitled")
|
||||
@@ -13991,6 +13995,7 @@ def _prepare_chat_start_session_for_stream(
|
||||
msg,
|
||||
attachments,
|
||||
s.pending_started_at,
|
||||
source=source,
|
||||
)
|
||||
s.save()
|
||||
|
||||
@@ -14097,6 +14102,7 @@ def _start_chat_stream_for_session(
|
||||
normalized_model: bool = False,
|
||||
diag=None,
|
||||
goal_related: bool = False,
|
||||
source: str = "webui",
|
||||
):
|
||||
"""Persist pending state, register an SSE channel, and start an agent turn."""
|
||||
attachments = attachments or []
|
||||
@@ -14167,6 +14173,7 @@ def _start_chat_stream_for_session(
|
||||
model=model,
|
||||
model_provider=model_provider,
|
||||
stream_id=stream_id,
|
||||
source=source,
|
||||
)
|
||||
break
|
||||
if needs_stale_cleanup:
|
||||
@@ -14343,6 +14350,7 @@ def _start_run(
|
||||
model_provider=request.provider or model_provider,
|
||||
normalized_model=normalized_model,
|
||||
diag=diag,
|
||||
source=request.source or source,
|
||||
)
|
||||
|
||||
def _legacy_adapter_factory():
|
||||
@@ -14381,6 +14389,7 @@ def _start_run(
|
||||
model_provider=model_provider,
|
||||
normalized_model=normalized_model,
|
||||
diag=diag,
|
||||
source=source,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -453,6 +453,7 @@ function _cancelMessageVirtualizedRender(){
|
||||
}
|
||||
function _messageIsRenderable(m){
|
||||
if(!m||!m.role||m.role==='tool') return false;
|
||||
if(m._source === 'process_wakeup') return false;
|
||||
if(_isContextCompactionMessage(m)||_isPreservedCompressionTaskListMessage(m)) return false;
|
||||
if(_isRecoveryControlMessage(m)) return false;
|
||||
const hasTc=Array.isArray(m.tool_calls)&&m.tool_calls.length>0;
|
||||
@@ -7285,6 +7286,7 @@ function getPendingSessionMessage(session, messagesOverride=null){
|
||||
attachments:attachments.length?attachments:undefined,
|
||||
_ts:session?.pending_started_at||Date.now()/1000,
|
||||
_pending:true,
|
||||
_source:session?.pending_user_source||undefined,
|
||||
};
|
||||
}
|
||||
async function checkInflightOnBoot(sid) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Test coverage for source field threading on process-wakeup synthetic turns."""
|
||||
|
||||
import time
|
||||
from api.models import Session, _append_recovered_pending_turn
|
||||
from api.routes import _checkpoint_user_message_for_eager_session_save
|
||||
|
||||
|
||||
def test_append_recovered_pending_turn_stamps_process_wakeup_source():
|
||||
"""Verify _append_recovered_pending_turn stamps _source when pending_user_source is process_wakeup."""
|
||||
s = Session(
|
||||
session_id="test-session-1",
|
||||
pending_user_message="[IMPORTANT: Process completed]",
|
||||
pending_user_source="process_wakeup",
|
||||
)
|
||||
recovered = _append_recovered_pending_turn(s)
|
||||
assert recovered is not None
|
||||
assert recovered["role"] == "user"
|
||||
assert recovered["content"] == "[IMPORTANT: Process completed]"
|
||||
assert recovered["_source"] == "process_wakeup"
|
||||
assert recovered["_recovered"] is True
|
||||
|
||||
|
||||
def test_append_recovered_pending_turn_skips_webui_source():
|
||||
"""Verify _append_recovered_pending_turn does NOT stamp _source when source is webui (default)."""
|
||||
s = Session(
|
||||
session_id="test-session-2",
|
||||
pending_user_message="Normal user message",
|
||||
pending_user_source="webui",
|
||||
)
|
||||
recovered = _append_recovered_pending_turn(s)
|
||||
assert recovered is not None
|
||||
assert recovered["role"] == "user"
|
||||
assert recovered["content"] == "Normal user message"
|
||||
assert "_source" not in recovered
|
||||
assert recovered["_recovered"] is True
|
||||
|
||||
|
||||
def test_append_recovered_pending_turn_defaults_to_no_source():
|
||||
"""Verify _append_recovered_pending_turn does NOT stamp _source when pending_user_source is None."""
|
||||
s = Session(
|
||||
session_id="test-session-3",
|
||||
pending_user_message="Another user message",
|
||||
pending_user_source=None,
|
||||
)
|
||||
recovered = _append_recovered_pending_turn(s)
|
||||
assert recovered is not None
|
||||
assert recovered["role"] == "user"
|
||||
assert "_source" not in recovered
|
||||
|
||||
|
||||
def test_checkpoint_user_message_stamps_process_wakeup_source():
|
||||
"""Verify eager-path checkpoint stamps _source on message dict when source is process_wakeup."""
|
||||
s = Session(session_id="test-session-4")
|
||||
s.messages = []
|
||||
_checkpoint_user_message_for_eager_session_save(
|
||||
s,
|
||||
msg="[IMPORTANT: Wakeup prompt]",
|
||||
attachments=[],
|
||||
started_at=time.time(),
|
||||
source="process_wakeup",
|
||||
)
|
||||
assert len(s.messages) == 1
|
||||
user_msg = s.messages[0]
|
||||
assert user_msg["role"] == "user"
|
||||
assert user_msg["content"] == "[IMPORTANT: Wakeup prompt]"
|
||||
assert user_msg["_source"] == "process_wakeup"
|
||||
|
||||
|
||||
def test_checkpoint_user_message_skips_webui_source():
|
||||
"""Verify eager-path checkpoint does NOT stamp _source when source is webui (default)."""
|
||||
s = Session(session_id="test-session-5")
|
||||
s.messages = []
|
||||
_checkpoint_user_message_for_eager_session_save(
|
||||
s,
|
||||
msg="Normal user message",
|
||||
attachments=[],
|
||||
started_at=time.time(),
|
||||
source="webui",
|
||||
)
|
||||
assert len(s.messages) == 1
|
||||
user_msg = s.messages[0]
|
||||
assert user_msg["role"] == "user"
|
||||
assert user_msg["content"] == "Normal user message"
|
||||
assert "_source" not in user_msg
|
||||
|
||||
|
||||
def test_checkpoint_user_message_defaults_to_no_source():
|
||||
"""Verify eager-path checkpoint does NOT stamp _source when source is omitted (defaults to webui)."""
|
||||
s = Session(session_id="test-session-6")
|
||||
s.messages = []
|
||||
_checkpoint_user_message_for_eager_session_save(
|
||||
s,
|
||||
msg="Another user message",
|
||||
attachments=[],
|
||||
started_at=time.time(),
|
||||
)
|
||||
assert len(s.messages) == 1
|
||||
user_msg = s.messages[0]
|
||||
assert user_msg["role"] == "user"
|
||||
assert user_msg["content"] == "Another user message"
|
||||
assert "_source" not in user_msg
|
||||
|
||||
|
||||
def test_session_pending_user_source_persisted():
|
||||
"""Verify pending_user_source survives serialization and deserialization."""
|
||||
s = Session(
|
||||
session_id="test-session-7",
|
||||
pending_user_message="Test message",
|
||||
pending_user_source="process_wakeup",
|
||||
)
|
||||
s_dict = {k: getattr(s, k, None) for k in [
|
||||
'session_id', 'pending_user_message', 'pending_user_source'
|
||||
]}
|
||||
assert s_dict["pending_user_source"] == "process_wakeup"
|
||||
|
||||
# Reconstruct from dict
|
||||
s2 = Session(**s_dict)
|
||||
assert s2.pending_user_source == "process_wakeup"
|
||||
Reference in new issue
Block a user