feat: persistent chat message storage + Android pull-to-load history
Backend:
- Add ChatMessage model + Alembic migration 024
- Add on_message callback to AgentRuntime for persisting messages during SSE streaming
- Plumb session_id from ChatRequest to AgentContext in all 4 chat endpoints
- Add GET /agent-chat/{id}/sessions and /sessions/{sid}/messages with cursor pagination
Android:
- Add DTOs/ApiService/MessageDao for server-side chat history
- ChatRepository: fetchOlderMessages (API + Room cache), offline fallback
- ChatViewModel: loadMoreHistory with isLoadingMore/hasMoreMessages state
- ChatScreen: scroll-to-top detection + top loading indicator
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,7 @@ class AgentRuntime:
|
||||
execution_logger: Optional[Any] = None,
|
||||
on_tool_executed: Optional[Callable[[str], Any]] = None,
|
||||
on_llm_call: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
||||
on_message: Optional[Callable[[Dict[str, Any]], Any]] = None,
|
||||
hook_manager: Optional[HookManager] = None,
|
||||
streamlined: bool = False,
|
||||
):
|
||||
@@ -141,6 +142,7 @@ class AgentRuntime:
|
||||
self.execution_logger = execution_logger
|
||||
self.on_tool_executed = on_tool_executed
|
||||
self.on_llm_call = on_llm_call
|
||||
self.on_message = on_message
|
||||
self._memory_context_loaded = False
|
||||
self._llm_invocations = 0
|
||||
# 自主学习作用域:bare 聊天用 "bare",Agent 用 "agent"
|
||||
@@ -808,6 +810,12 @@ class AgentRuntime:
|
||||
|
||||
# 2. 追加用户消息
|
||||
self.context.add_user_message(user_input)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "user", "content": user_input,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": 0,
|
||||
})
|
||||
|
||||
# 2.5 计划模式 (P2) — 流式生成执行计划
|
||||
plan: Optional[Plan] = None
|
||||
@@ -969,6 +977,12 @@ class AgentRuntime:
|
||||
# LLM 直接返回文本 → 结束
|
||||
self.context.add_assistant_message(content)
|
||||
final_text = content or "(模型未返回有效内容)"
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "assistant", "content": final_text,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
})
|
||||
review_score = 0.0
|
||||
|
||||
# 输出质量自检(默认关闭)
|
||||
@@ -1027,6 +1041,12 @@ class AgentRuntime:
|
||||
|
||||
# 有工具调用 → 先记录 assistant 消息
|
||||
self.context.add_assistant_message(content or "", tool_calls, reasoning)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "assistant", "content": content or "",
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
})
|
||||
|
||||
# yield think 事件
|
||||
tc_names = [tc["function"]["name"] for tc in tool_calls]
|
||||
@@ -1087,6 +1107,15 @@ class AgentRuntime:
|
||||
result = json.dumps({"error": hook_res.reason}, ensure_ascii=False)
|
||||
yield {"type": "tool_result", "name": tname, "result": result, "iteration": self.context.iteration}
|
||||
self.context.add_tool_result(tcid, tname, result)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "tool", "content": result,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
"tool_name": tname,
|
||||
"tool_input": json.dumps(targs, ensure_ascii=False) if targs else None,
|
||||
"tool_output": result,
|
||||
})
|
||||
continue
|
||||
if hook_res.modified_input:
|
||||
targs = hook_res.modified_input
|
||||
@@ -1119,11 +1148,29 @@ class AgentRuntime:
|
||||
result = f"[审批拒绝] 工具 {tname} 需要人工审批但被拒绝。"
|
||||
yield {"type": "tool_result", "name": tname, "result": result, "iteration": self.context.iteration}
|
||||
self.context.add_tool_result(tcid, tname, result)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "tool", "content": result,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
"tool_name": tname,
|
||||
"tool_input": json.dumps(targs, ensure_ascii=False) if targs else None,
|
||||
"tool_output": result,
|
||||
})
|
||||
continue
|
||||
elif decision == "skip":
|
||||
result = f"[审批跳过] 工具 {tname} 被跳过。"
|
||||
yield {"type": "tool_result", "name": tname, "result": result, "iteration": self.context.iteration}
|
||||
self.context.add_tool_result(tcid, tname, result)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "tool", "content": result,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
"tool_name": tname,
|
||||
"tool_input": json.dumps(targs, ensure_ascii=False) if targs else None,
|
||||
"tool_output": result,
|
||||
})
|
||||
continue
|
||||
# decision == "approved" → 继续执行
|
||||
|
||||
@@ -1154,6 +1201,15 @@ class AgentRuntime:
|
||||
))
|
||||
|
||||
self.context.add_tool_result(tcid, tname, result)
|
||||
if self.on_message:
|
||||
self.on_message({
|
||||
"role": "tool", "content": result,
|
||||
"session_id": self.context.session_id,
|
||||
"iteration": self.context.iteration,
|
||||
"tool_name": tname,
|
||||
"tool_input": json.dumps(targs, ensure_ascii=False) if targs else None,
|
||||
"tool_output": result,
|
||||
})
|
||||
self.context.tool_calls_made += 1
|
||||
|
||||
# Hook: PostToolUse — 工具执行后处理 (流式)
|
||||
|
||||
Reference in New Issue
Block a user