Compare commits

..

6 Commits

Author SHA1 Message Date
Lluis Agusti
b6b605f936 fix(frontend): navbar border, sidebar padding, QA fake pulse chips
- Navbar: add 1px solid #f1f1f1 border-bottom to nav element
- ChatSidebar: reduce SidebarHeader padding from pt-4/pb-4 to pt-3/pb-3
- PulseChips: re-add QA fake chips with long text for overflow testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 21:03:06 +07:00
Lluis Agusti
29215e9523 fix(frontend): check refreshSession return value instead of catching
refreshSession() never throws — it returns { error } on failure.
The try/catch was dead code, causing stale session data on refresh
failure. Now checks the return value and shows an error toast.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:58:39 +07:00
Lluis Agusti
6456e0f317 Merge branch 'fix/small-ui-fixes' of https://github.com/Significant-Gravitas/AutoGPT into fix/small-ui-fixes 2026-04-16 19:48:14 +07:00
Lluis Agusti
12fae3132a fix(frontend): validate and trim inputs in PUT /api/auth/user
Catch malformed JSON with a 400 instead of letting it become a 500.
Validate that email/full_name are strings and trim whitespace before
passing to Supabase.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:48:09 +07:00
Ubbe
1a38974f19 Merge branch 'dev' into fix/small-ui-fixes 2026-04-16 19:28:39 +07:00
Lluis Agusti
9c5d1e1019 fix(frontend): small UI fixes — sort menu bg, name update auth, stats grid overflow, pulse chips
- LibrarySortMenu/AgentFilterMenu: force transparent bg on legacy SelectTrigger
- EditNameDialog: use server-side API route instead of client-side Supabase call
  to fix "Auth session missing!" error caused by httpOnly cookies
- /api/auth/user route: accept full_name alongside email
- StatsGrid: use OverflowText for tile labels to truncate with tooltip
- PulseChips: fixed-width chips (15rem) with horizontal scroll and styled scrollbar
- Tests: update EditNameDialog tests for fetch-based flow, add PulseChips tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:13:53 +07:00
7 changed files with 45 additions and 171 deletions

1
.gitignore vendored
View File

@@ -195,4 +195,3 @@ test.db
# Implementation plans (generated by AI agents)
plans/
.claude/worktrees/
test-results/

View File

@@ -548,46 +548,6 @@ async def update_message_content_by_sequence(
return False
async def update_message_tool_calls(
session_id: str,
sequence: int,
tool_calls: list[dict],
) -> bool:
"""Patch the toolCalls column of an already-saved assistant message.
Called when StreamToolInputAvailable arrives after an intermediate flush
saved the assistant message with tool_calls=None. The DB save is
append-only (uses get_next_sequence), so the already-persisted row must
be updated in-place to reflect the tool_calls that arrived later.
Args:
session_id: The chat session ID.
sequence: The 0-based sequence number of the assistant message to patch.
tool_calls: The full list of tool call dicts to set on the row.
Returns:
True if the row was found and updated, False otherwise.
"""
try:
result = await PrismaChatMessage.prisma().update_many(
where={"sessionId": session_id, "sequence": sequence},
data={"toolCalls": SafeJson(tool_calls)},
)
if result == 0:
logger.warning(
f"update_message_tool_calls: no row found for session {session_id}, "
f"sequence {sequence}"
)
return False
return True
except Exception as e:
logger.error(
f"update_message_tool_calls failed for session {session_id}, "
f"sequence {sequence}: {e}"
)
return False
async def set_turn_duration(session_id: str, duration_ms: int) -> None:
"""Set durationMs on the last assistant message in a session.

View File

@@ -44,7 +44,6 @@ from backend.util.exceptions import NotFoundError
from backend.util.settings import Settings
from ..config import ChatConfig, CopilotLlmModel, CopilotMode
from ..db import update_message_tool_calls
from ..constants import (
COPILOT_ERROR_PREFIX,
COPILOT_RETRYABLE_ERROR_PREFIX,
@@ -2263,30 +2262,6 @@ async def _run_stream_attempt(
if dispatched is not None:
yield dispatched
# If tool calls arrived this batch AND the assistant message was
# already flushed to DB (sequence is set), patch the existing row
# so tool_calls are not lost. The append-only save (start_sequence)
# in _save_session_to_db never re-saves already-persisted rows, so
# without this patch the assistant row keeps tool_calls=null.
if acc.assistant_response.sequence is not None and any(
isinstance(r, StreamToolInputAvailable) for r in adapter_responses
):
try:
await asyncio.shield(
update_message_tool_calls(
ctx.session.session_id,
acc.assistant_response.sequence,
acc.accumulated_tool_calls,
)
)
except Exception as patch_err:
logger.warning(
"%s tool_calls DB patch failed (sequence=%d): %s",
ctx.log_prefix,
acc.assistant_response.sequence,
patch_err,
)
# Append assistant entry AFTER convert_message so that
# any stashed tool results from the previous turn are
# recorded first, preserving the required API order:

View File

@@ -20,11 +20,7 @@ from datetime import datetime, timezone
from unittest.mock import MagicMock
from backend.copilot.model import ChatMessage, ChatSession
from backend.copilot.response_model import (
StreamStartStep,
StreamTextDelta,
StreamToolInputAvailable,
)
from backend.copilot.response_model import StreamStartStep, StreamTextDelta
from backend.copilot.sdk.service import _dispatch_response, _StreamAccumulator
_NOW = datetime(2024, 1, 1, tzinfo=timezone.utc)
@@ -219,100 +215,3 @@ class TestPreCreateAssistantMessage:
_simulate_pre_create(acc, ctx)
assert len(ctx.session.messages) == 0
class TestToolCallsLostAfterIntermediateFlush:
"""Regression tests for the bug where tool_calls are lost when an
intermediate flush saves the assistant message before StreamToolInputAvailable
arrives.
Sequence that triggers the bug:
1. StreamTextDelta → assistant message appended with tool_calls=None
2. Intermediate flush fires (time/count threshold) → DB row written with tool_calls=null
and acc.assistant_response.sequence is set (back-filled)
3. StreamToolInputAvailable → acc.assistant_response.tool_calls mutated in-memory
4. Final save: append-only — assistant row already in DB, tool_calls never updated
Fix: when StreamToolInputAvailable arrives and acc.assistant_response.sequence
is not None, issue a DB UPDATE to patch toolCalls on the existing row.
"""
def test_text_delta_then_tool_input_sets_tool_calls_on_message(self) -> None:
"""After text arrives then tool input arrives, acc.assistant_response.tool_calls
should be populated regardless of flush state."""
session = _make_session()
ctx = _make_ctx(session)
state = _make_state()
acc = _StreamAccumulator(
assistant_response=ChatMessage(role="assistant", content=""),
accumulated_tool_calls=[],
)
# Step 1: text delta arrives, message appended
_dispatch_response(
StreamTextDelta(id="t1", delta="Let me run that for you."),
acc,
ctx,
state,
False,
"[test]",
)
assert acc.has_appended_assistant
assert session.messages[-1].tool_calls is None
# Step 2: simulate intermediate flush back-filling the sequence
acc.assistant_response.sequence = 1 # back-filled by _save_session_to_db
# Step 3: tool input arrives
_dispatch_response(
StreamToolInputAvailable(
toolCallId="call_abc",
toolName="bash_exec",
input={"command": "ls"},
),
acc,
ctx,
state,
False,
"[test]",
)
# tool_calls should be set in memory
assert acc.assistant_response.tool_calls is not None
assert len(acc.assistant_response.tool_calls) == 1
assert acc.assistant_response.tool_calls[0]["id"] == "call_abc"
def test_sequence_set_when_flush_occurred_before_tool_input(self) -> None:
"""When sequence is back-filled (flush happened) before tool calls arrive,
it is detectable so the caller can issue a DB patch."""
acc = _StreamAccumulator(
assistant_response=ChatMessage(role="assistant", content="hello"),
accumulated_tool_calls=[],
has_appended_assistant=True,
)
# Simulate flush back-fill
acc.assistant_response.sequence = 3
ctx = _make_ctx()
state = _make_state()
_dispatch_response(
StreamToolInputAvailable(
toolCallId="call_xyz",
toolName="run_block",
input={},
),
acc,
ctx,
state,
False,
"[test]",
)
# Caller should detect this condition and issue a DB patch
needs_db_patch = acc.assistant_response.sequence is not None and bool(
acc.accumulated_tool_calls
)
assert (
needs_db_patch
), "Expected needs_db_patch=True when flush happened before tool calls arrived"

View File

@@ -246,7 +246,7 @@ export function ChatSidebar() {
</SidebarHeader>
)}
{!isCollapsed && (
<SidebarHeader className="shrink-0 px-4 pb-4 pt-4 shadow-[0_4px_6px_-1px_rgba(0,0,0,0.05)]">
<SidebarHeader className="shrink-0 px-4 pb-3 pt-3 shadow-[0_4px_6px_-1px_rgba(0,0,0,0.05)]">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}

View File

@@ -5,13 +5,53 @@ import { useSitrepItems } from "@/app/(platform)/library/components/SitrepItem/u
import type { PulseChipData } from "./types";
import { useMemo } from "react";
// TODO: remove QA fakes before merging
const QA_FAKES: PulseChipData[] = [
{
id: "qa-1",
agentID: "fake-1",
name: "SEO Blog Writer with Advanced Keyword Research and Content Optimization",
status: "running",
priority: "running",
shortMessage:
"Writing a comprehensive long-form article on the latest AI trends in enterprise software development and deployment",
},
{
id: "qa-2",
agentID: "fake-2",
name: "Multi-Cloud Data Pipeline Monitor and Alerting System",
status: "error",
priority: "error",
shortMessage:
"Connection to the primary data warehouse timed out after 30 retries — fallback region also unreachable",
},
{
id: "qa-3",
agentID: "fake-3",
name: "Social Media Cross-Platform Scheduler and Analytics Dashboard",
status: "idle",
priority: "success",
shortMessage:
"All 12 scheduled posts across Twitter, LinkedIn, and Instagram were published successfully with engagement tracking enabled",
},
{
id: "qa-4",
agentID: "fake-4",
name: "Customer Support Triage and Automatic Escalation Handler",
status: "running",
priority: "stale",
shortMessage:
"3 high-priority tickets awaiting classification — SLA breach warning for 2 enterprise accounts pending review",
},
];
export function usePulseChips(): PulseChipData[] {
const { agents } = useLibraryAgents();
const sitrepItems = useSitrepItems(agents, 5);
return useMemo(() => {
return sitrepItems.map((item) => ({
const real = sitrepItems.map((item) => ({
id: item.id,
agentID: item.agentID,
name: item.agentName,
@@ -19,5 +59,6 @@ export function usePulseChips(): PulseChipData[] {
priority: item.priority,
shortMessage: item.message,
}));
return [...real, ...QA_FAKES];
}, [sitrepItems]);
}

View File

@@ -60,7 +60,7 @@ export function Navbar() {
<PreviewBanner branchName={previewBranchName} />
) : null}
<nav
className="inline-flex w-full items-center bg-[#FAFAFA]/80 p-3 backdrop-blur-xl"
className="inline-flex w-full items-center border-b border-[#f1f1f1] bg-[#FAFAFA]/80 p-3 backdrop-blur-xl"
style={{ height: NAVBAR_HEIGHT_PX }}
>
{/* Left section */}