From d9c973d6e38cd91d2841ae8bd8df51e0962bd60c Mon Sep 17 00:00:00 2001 From: Nipuna Rangika Date: Mon, 10 Aug 2026 16:24:44 +0530 Subject: [PATCH 1/3] fix: resolve flow session issues --- .../flow/doctype/flow_session/flow_session.py | 20 +++++++++++++++---- .../flow_session_message.json | 2 +- .../flow_session_message.py | 2 +- flow/lib/agent.py | 3 ++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/flow/flow/doctype/flow_session/flow_session.py b/flow/flow/doctype/flow_session/flow_session.py index 2ef4c04..5f0c756 100644 --- a/flow/flow/doctype/flow_session/flow_session.py +++ b/flow/flow/doctype/flow_session/flow_session.py @@ -103,7 +103,8 @@ def clear_old_logs(days=30): def transcript(self) -> list[dict[str, Any]]: """Return the conversation history in OpenAI message format.""" - return [_row_to_message(row) for row in self.messages] + tool_call_id_to_name: dict[str, str] = {} + return [_row_to_message(row, tool_call_id_to_name) for row in self.messages] def append_run_messages(self, new_messages: list[dict[str, Any]], run: str) -> None: """Append the messages produced by `run` to this session's transcript. @@ -444,13 +445,24 @@ def _route_attachment(text: str | None, threshold: int, embeddings_on: bool) -> return "Retrieval" if embeddings_on else "Inline" -def _row_to_message(row) -> dict[str, Any]: +def _row_to_message(row, tool_call_id_to_name: dict[str, str] | None = None) -> dict[str, Any]: """Convert a stored transcript row to an OpenAI-format message dict.""" if row.role == "tool": - return {"role": "tool", "tool_call_id": row.tool_call_id, "content": row.content or ""} + msg: dict[str, Any] = {"role": "tool", "tool_call_id": row.tool_call_id, "content": row.content or ""} + if tool_call_id_to_name and row.tool_call_id in tool_call_id_to_name: + msg["name"] = tool_call_id_to_name[row.tool_call_id] + return msg message: dict[str, Any] = {"role": row.role, "content": row.content} if row.tool_calls: - message["tool_calls"] = json.loads(row.tool_calls) + try: + parsed_calls = json.loads(row.tool_calls) + message["tool_calls"] = parsed_calls + if tool_call_id_to_name is not None: + for tc in parsed_calls or []: + if isinstance(tc, dict) and tc.get("id") and tc.get("function", {}).get("name"): + tool_call_id_to_name[tc["id"]] = tc["function"]["name"] + except (ValueError, TypeError): + pass return message diff --git a/flow/flow/doctype/flow_session_message/flow_session_message.json b/flow/flow/doctype/flow_session_message/flow_session_message.json index 6e5debd..edff161 100644 --- a/flow/flow/doctype/flow_session_message/flow_session_message.json +++ b/flow/flow/doctype/flow_session_message/flow_session_message.json @@ -31,7 +31,7 @@ { "description": "When role is 'tool', the assistant tool_call.id this result answers.", "fieldname": "tool_call_id", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Tool Call ID" }, { diff --git a/flow/flow/doctype/flow_session_message/flow_session_message.py b/flow/flow/doctype/flow_session_message/flow_session_message.py index 8e7db38..ad34dbf 100644 --- a/flow/flow/doctype/flow_session_message/flow_session_message.py +++ b/flow/flow/doctype/flow_session_message/flow_session_message.py @@ -19,7 +19,7 @@ class FlowSessionMessage(Document): parenttype: DF.Data role: DF.Data run: DF.Link | None - tool_call_id: DF.Data | None + tool_call_id: DF.SmallText | None tool_calls: DF.JSON | None # end: auto-generated types diff --git a/flow/lib/agent.py b/flow/lib/agent.py index 84aa2bc..58fb516 100644 --- a/flow/lib/agent.py +++ b/flow/lib/agent.py @@ -205,7 +205,7 @@ def _prepare_resume( content = self._resolve_confirmation(call, answer) else: content = _serialize_tool_result(answer) - messages.append({"role": "tool", "tool_call_id": call.id, "content": content}) + messages.append({"role": "tool", "tool_call_id": call.id, "name": call.name, "content": content}) resolved.append((call, content)) return messages, resolved @@ -260,6 +260,7 @@ def _loop( { "role": "tool", "tool_call_id": call.id, + "name": call.name, "content": _serialize_tool_result(result), } ) From 683a98d3bbdcf84e848d3c4f950eee06b9b085b3 Mon Sep 17 00:00:00 2001 From: Nipuna Rangika Date: Mon, 10 Aug 2026 16:33:05 +0530 Subject: [PATCH 2/3] feat(ui): render stream and session errors in styled red error box --- frontend/src/components/AssistantMessage.vue | 10 ++++-- frontend/src/components/ErrorMessage.vue | 30 ++++++++++++++++ frontend/src/store.js | 36 ++++++++++++++++++-- 3 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/ErrorMessage.vue diff --git a/frontend/src/components/AssistantMessage.vue b/frontend/src/components/AssistantMessage.vue index ece6807..1701992 100644 --- a/frontend/src/components/AssistantMessage.vue +++ b/frontend/src/components/AssistantMessage.vue @@ -5,6 +5,7 @@ import ActivityGroup from "./ActivityGroup.vue"; import ConfirmCard from "./ConfirmCard.vue"; import FeedbackBar from "./FeedbackBar.vue"; import WorkingIndicator from "./WorkingIndicator.vue"; +import ErrorMessage from "./ErrorMessage.vue"; import { useStore } from "@/store"; const props = defineProps({ message: { type: Object, required: true } }); @@ -18,11 +19,15 @@ function isApproval(part) { return toolApproval.value[part.name] === true || q !== undefined || part.approval !== null; } -// Group parts for render: text → prose, approval tool → own line (card if pending), +// Group parts for render: text → prose, error → red error box, approval tool → own line (card if pending), // other tools → merged activity group. Rendering-only. const items = computed(() => { const out = []; for (const part of props.message.parts) { + if (part.type === "error") { + out.push({ kind: "error", id: part.id, part }); + continue; + } if (part.type !== "tool") { out.push({ kind: "text", id: part.id, part }); continue; @@ -61,7 +66,8 @@ const hovered = ref(false); @mouseleave="hovered = false" >